以下将介绍application/json
,multipart/form-data
,application/x-www-form-urlencoded
三种 Content-Type 传参情况下,Spring MVC 控制器中参数绑定的方式。
1. 前置内容
1.1 关于三种 Content-Type
这里主要介绍三种 Content-Type:
-
multipart/form-data
请求中既可以携带文件,又可以携带参数。其中参数以键值对的方式传递,参数之间、参数与文件之间以content-disposition
分隔; -
application/x-www-form-urlencoded
只能上传参数,不能携带文件,参数通过?xxx=xxx&xxx=xxx
的方式被组织在一起; -
application/json
只能上传参数,不能携带文件,参数不被特殊组织,保持原 JSON 字符串的形式。
1.2 扩展:浏览器调试工具中请求参数的形式
在前端发送请求时,我们可以通过浏览器看到请求的参数。在浏览器调试工具中,参数栏会有多种标题:
- Query String Parameters
当使用 GET 方式提交请求时,采用这一标题
- Request Payload
当使用 application/json 方式提交时,采用这一标题
- Form Data
当使用 multipart/form-data
或 application/x-www-form-urlencoded
方式提交时,采用这一标题,注意这两种 form-data 的区别。
1.3 测试数据
这里采用嵌套数据如下:
{
"username": "dailybird",
"password": "dailybirdo",
"ids": [1,2,3],
"detail": {
"gender": "male",
"location": "Beijing",
"ids": [4,5,6]
}
}
注:与文件上传相关的参数后面会单独提到,这里先进行非文件参数提交的实验。
1.4 预期绑定的对象
这里,仿照请求参数的格式创建 User
对象,我们试图将请求参数绑定到该对象上。这里使用 Lombok
来减少 setter
的创建:
@ToString
@Data
public class User {
private String username;
private String password;
private List<Integer> ids;
private Detail detail;
@Data
public static class Detail {
private String gender;
private String location;
private List<Integer> ids;
}
}
2. 绑定方式
2.1 利用 @RequestBody 解析 application/json 的 POST请求
控制器代码如下:
@RequestMapping(value = "/application/json")
public String applicationJson(@RequestBody User user) {
log.info("{}", user.toString());
return user.toString();
}
当使用 POST,并携带 Content-Type: application/json
头发送请求时,控制器能够完全解析嵌套的参数。
注:由于 @RequestBody 本身是调用 HttpMessageConverter
解析请求体中的数据,而 GET 方式的参数不会存在于请求体中,所以 @RequestBody 不能处理 GET 方式的请求。
2.2 利用 @RequestParam 接收 multipart/form-data 及 application/x-www-form-urlencoded 中的请求
控制器代码如下:
public String xWwwFormUrlencoded(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("ids")List<Integer> ids,
@RequestParam("detail") Detail detail) {
log.info("{}, {}, {}, {}", username, password, ids, detail);
return "";
}
其中 Detail 类为与之前 User 内部类等同的类。
2.2.1 application/x-www-form-urlencoded
这里我们借助 jquery 的相关函数进行测试:
$.post("http://localhost:8083/application/x-www-form-urlencoded", {
"username": "aaa",
"password": "bbb",
"ids": [1,2,3],
"detail": {
"gender": "ccc",
"location": "ddd",
"ids": [4,5,6]
}
})
然后我们收到了如下提示:
Required List parameter 'ids' is not present
但我们确实已经发送了 ids
参数,为什么没有获取到呢?这一点我们放到之后再谈,先试一下 multipart/form-data 的方式。
2.2.2 multipart/form-data
当然,采用这一方式,我们会收到同样的提示:
Required List parameter 'ids' is not present
2.2.3 参数接收到的问题
让我们在浏览器的开发者工具中看一看请求参数实际的样子:
不同于 PHP 框架 Laravel,@RequestParam 并不会将 ids[]
之类的数组类参数和 detail[xxx]
之类的嵌套参数进行重组。因而,控制器会认为收到了 ids[]
参数,而不是 ids
参数,同理也适用于嵌套参数。
那我们该怎么做的?我在 Stack Overflow 上得到了解答,我们可以采用以下方法之一:
- 将
ids[]
改为ids
传参,即ids=1&ids=2&...
的方式( 注意对比上图 ),将嵌套类参数detail[gender]
等改为detail.gender
; - 将嵌套参数采用 Map 类型接收。
注:在 Spring MVC 中,我们可以不书写 @RequestParam,直接使用相与请求参数同名的变量进行接收( 或直接使用一个 POJO 对象 ),但该方式也存在着与以上相同的问题。
2.3 文件上传问题
最开始已经说过,若要上传文件,在上述三种 Content-Type 中,只能使用 multipart/form-data,在注意到 2.2 中所提到的问题后,我们便可以通过 MultipartFile
类型的属性来获取到文件参数了。
3. 总结
从 Laravel 过渡到 Spring Boot,确实感到了在控制器层面二者的差异( 当然在 DAO 层更是如此 ),以下给出一个列表,用以纪念自己踩的坑:
参考链接
- postman中 form-data、x-www-form-urlencoded、raw、binary的区别 - CSDN博客
- http - What's the difference between "Request Payload" vs "Form Data" as seen in Chrome dev tools Network tab - Stack Overflow
- spring - What is difference between @RequestBody and @RequestParam? - Stack Overflow
- 浅谈@RequestMapping @ResponseBody 和 @RequestBody 注解的用法与区别 - CSDN博客
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。