Spring中的@RequestBody注解与常规的HTTP方法的传值方式

Posted 今夕是何夕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中的@RequestBody注解与常规的HTTP方法的传值方式相关的知识,希望对你有一定的参考价值。

地址:https://super-aviator.github.io/2019/08/30/Spring%E4%B8%AD%E7%9A%84-RequestBody%E6%B3%A8%E8%A7%A3/

上周进行项目开发的时候,发现前端的含有文件的表单数据时后台接收报错,异常的大致意思是表单类型不支持,我也是有点蒙逼,以前也遇到过这种问题,加上@RequestBody就可以,所以这次我也加上了@RequestBody注解,结果还是报错,这时前端发消息过来,告诉我不能加@RequestBody,我去掉之后果然好了,好吧。一直没弄明白什么时候要加@RequestBody什么时候不要加,趁着这个迭代的任务很轻松,学习一下Spring中@RequestBody注解和后端如何接收常用的HTTP方法传过来的数据,结合PostMan对结果进行测试。

@RequestBody注解

@RequestBody注解常用来处理POST请求,并且content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。

@RequestMapping注解的方法的参数中包含了@RequestBody注解,那么Spring会首先查看请求中的Content-Type头部,然后根据Content-Type头部去查找合适的HttpMessageConverter

例如,如果客户端发送的Spittle数据是JSON表述形式,那 么Content-Type头部信息可能就会是“application/json”。在 这种情况下,DispatcherServlet会查找能够将JSON转换为Java 对象的消息转换器。如果Jackson 2库在类路径中,那 么MappingJackson2HttpMessageConverter将会担此重任,将 JSON表述转换为Spittle,然后传递到saveSpittle()方法中。 这个方法还使用了@ResponseBody注解,因此方法返回的Spittle 对象将会转换为某种资源表述,发送给客户端。

在《Spring 实战》中,表明了@RequestBody注解的含义和使用方式:用来解析请求体(可能是POST,PUT,DELETE,GET请求)中Content-Type为application/json类型的请求,利用消息转换器将其转换为对应的java对象(必须使用VO对象去接收),那么什么类型的消息能够加上@RequestBody,什么类型的消息不能加呢?

表单类型

MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。Content-Type头部后面可以追加;charset=UTF-8指定编码格式,例如:Content-Type:x-www-from-urlencoded;charset=UTF-8

Content-Type字段表明了请求的请求体类型,可以是如下几种常见的类型:
常见媒体格式如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式
  • multipart/form-data:(体数据被编码为一条消息,页上的每个控件对应消息中的一个部分,这个一般文件上传时用)

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

下面是常见的表单提交的Content-Type的取值:

application/x-www-form-urlencoded

  • 如果使用的是GET请求,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割,加载这个新的url。

  • 如果使用的是POST请求,会采用类似GET的字符串拼接的方式,将拼接的key-value字段放到body里面,而非url的后面,所以POST请求的url长度是没有限制的,因为拼接的url请求参数都存放在body里面,如下

1
2
3
4
5
6
7
   POST  HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: e8c31d7a-fd52-cbf2-2741-07d73cb1101b

keyword=panda&author=zane

注意:使用application/x-www-form-urlencoded编码方式的请求会对所有非ASCII的字符使用%HH的方式进行转换,所以一个非ASCII字符会由三个字符去表示,当非ASCII非常多时,会增加大约三倍的带宽,这无疑是一种浪费。

multipart/form-data

如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,Content-Type就会升级使用multipart/form-data类型。浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件的name,即字段名)等信息,并加上分割符(boundary),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST / HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 090df52c-d103-279b-1479-50e6a7fef58b

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="keyword"

panda
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="author"

zane
------WebKitFormBoundary7MA4YWxkTrZu0gW

上面的请求中,keyword键对应的值为panda,author键对应的值为zane。

首先我们看请求头的 Content-Type 它除了正常的 multipart/form-data 外还多了一个 boundary ,这个 boundary 的意思和字面意思一样就是分界线,通过分界线将每个键值对用 boundary 分割开来以示区别,这个分界线是特殊选择出来的,以便该boundary不会出现在任何有效负载中。现在我们看请求体,我们注意到boundary 将键值对分割后的每一部分都有 Content-Disposition 字段,实际上该字段的值必须为 form-data 而且后面必须加上 name 指定这部分的键名,然后是一行空行,空行之后便是提交数据的内容。 之所以要弄的这么复杂是因为 multipart/form-data 要支持文件上传。

注意:使用multipart/form-data编码方式的请求不会对非ASCII字符进行转码,所以也就不会有消耗,但是对于简短的字母数字值(与大多数web表单一样),添加所有MIME头的开销将大大超过更有效的二进制编码所节省的开销。

application/json

数据以纯文本形式(text/json/xml/html)进行编码,POST方法使用这种方式会把表单的键值对以一个JSON字符串的方式放到HTTP的body里面。例如如下的postman中的请求示例:
?
技术图片

注意:当非post请求的请求体中也含有JSON字符串时,依旧可以使用@RequestBody拿到请求体中的数据。

Postman测试

下面使用Postman来测试一下什么时候要加@Requestbody,什么时候不用加@RequestBody注解:
首先是Controller中的代码,有个方法,一个使用了@RequestBody注解,一个没有使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
@RequestMapping("/requestBody")
@Slf4j
public class RequestBodyController {
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
private static class Request{
private String name;
private Integer age;
@JSONField(serialize = false,deserialize = false)
private MultipartFile avatar;
}

@PostMapping("/request")
public HttpEntity<Request> generalRequest(Request request) {
log.info("generalRequest-{}",JSON.toJSONString(request));
return new HttpEntity<>(request);
}

@PostMapping("/requestBody")
public HttpEntity<Request> generalRequestBody(@RequestBody Request request) {
log.info("generalRequest-{}",JSON.toJSONString(request));
return new HttpEntity<>(request);
}
}

当请求中的ContentType分别为一下三种类型时,结果如下:

否加上注解ContentTypex-www-form-urlencodedform-dataapplication/json
不加@RequestBody注解 能接收 能接收 不能接收
加上@RequestBody注解 不能接收 不能接收 能接收

@RequestBody使用总结

@RequestBody用于需要触发HttpMessageConverter的场景:

  • 当HTTP请求的Content-Type头部为application/json时,需要加上@RequestBody注解,并使用默认的HttpMessageConverter或者自定义的HttpMessageConverter对请求的body中的json字符串转换为java对象。
  • 当Content-Type头部的值为application/x-www-form-urlencoded或者multipart/form-data时,表名此请求是一个常规的表单请求,不能使用@RequestBody注解。

注意:使用@RequestBody注解的参数必须使用VO对象的方式去接收,否则会接收不到参数

参考地址:

以上是关于Spring中的@RequestBody注解与常规的HTTP方法的传值方式的主要内容,如果未能解决你的问题,请参考以下文章

Spring中的@RequestBody和@ResponseBody注解

自定义spring参数注解 - 打破@RequestBody单体限制

Spring中的注解 @RequestBody和@ResponseBody的使用和区别

@RequestBody注解原理

使用Spring的@RequestBody注解,无法映射首字母大写属性的踩坑记录

Spring中的注解@