Spring MVC是如何支持application/json的?

背景

涛哥最近跟我提了个需求,要求在http请求的过程中直接传输json字符串,问spring mvc在后端能不能直接拿到参数,一开始笔者第一想法是在spring mvc的Controller层的方法里把@PathVariable注解去掉,就能正常读取到传递过来的值,后来想想不太对,如果请求体里是正常的key=value形式,去掉@PathVariable是有效的,直接在body里面放个json好像不太行吧;然后涛哥把前端ajax请求代码发给我看,上面指明了request的content-type是application/json!

关键的Content-Type

平时在处理post请求的时候并没有太留意,大部分post都是来自表单提交和ajax的post请求,默认情况下,请求体里的格式都是key=value&key=value形式,这种编码方式其实就是最常用的application/x-www-form-urlencoded,只是平时我们不会主动去写这个content-type而已;现在主动指明body里面的数据以json格式传输,即可指明content-type为application/json。注意:jQuery的ajax方法指定的dataType=json是指明接收json格式的返回数据,对应http header中accept:application/json.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$(function(){
$.ajax({
url: "http://localhost:8080/demo",
type: "POST",
dataType: "json",
contentType : "application/json",
data : JSON.stringify({username:'1', password:'2'}),
success : function(data){
console.log("success");
console.log(data);
},
error: function(e){
console.log("error");
console.log(e);
}
});
});

@RequestBody注解

一开始并不知道Spring MVC提供了这个注解,涛哥说不想用这个注解,我看走眼以为是@ResponseBody,这个注解会直接把返回的对象以json形式写在body里;而@RequestBody注解的含义就是读取请求body里面的值映射成参数。之后自己动手实验了一把,后端代码如下:

1
2
3
4
5
6
7
8
@RequestMapping(value = "/demo", method = RequestMethod.POST)
@ResponseBody
public Map index(HttpServletRequest request, @RequestBody UserEntity user) {
Map<String, String> map = new HashMap<String, String>();
map.put("username", user.getUsername());
map.put("password", user.getPassword());
return map;
}

其实一开始并不顺利,总是报“415 Unsupported Media Type”这种错,debug输出的log显示不支持application/json这种media-type

1
[DEBUG] Resolving exception from handler [com.yuanwhy.fantasy.controller.HomeController@6ea53502]: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json' not supported - org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)

后来发现没有使用明确指明使用注解驱动,加上后就好了,所以这里有时不加虽然也能使用@Controller这种注解,但是最好还是一开始就加上,否则总有一些让人抓狂的默认行为。
调试时控制台打印结果显示成功读取到服务端返回的正确参数,其实直接从httpServletRequest里也是能读取到body信息的,只是太麻烦而已。