简介
SpringMvc的方法里我们可以接受各式各样类型的参数,Stirng、Integer、@RequestBody(Json)、ModelAndView(Spring自动注入的一些参数)等,那么SpringMvc是如何将这些参数注入的呢?
例子
分别用postman访问上面4个接口
除了第一个接口调用成功,另外三个接口均报错了。为什么会报错呢?@RequestBody、@RequestParam应该在什么情况下使用呢?为什日期格式无法转换呢?
SpringMvc源码
两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler,这两个接口都是Spring3.1版本之后加入的。
SpringMvc请求的入口是DispatcherServlet
进入到父类AbstractHandlerMethodAdapter中
进入到RequestMappingHandlerAdapter类中跟进handleInternal方法
进入到ServletInvocableHandlerMethod类
进入InvocableHandlerMethod类
进入HandlerMethodArgumentResolverComposite类
methodArgumentResolver.supportsParameter(parameter) 这个方法就是判断当前遍历的resolver和参数是不是匹配,不通的reslover都有自己的实现。当找到对应的resovler后,就用resolver去处理参数了。我们再回到之前的代码。
我们通过源码发现,RequestResponseBodyMethodProcessor这个类其实同时实现了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver这两个接口。所以这里实际上调用是该类的方法,我们继续跟进。
处理请求的时候使用内部的readWithMessageConverters方法。
到这里处理入参的流程就结束了,总结一下就是先找到对应的reslover,然后用reslover去处理参数。
下面来我们来看看常用的HandlerMethodArgumentResolver实现类(本文粗略讲下,有兴趣的读者可自行研究)。
1. RequestParamMethodArgumentResolver
支持带有@RequestParam注解的参数或带有MultipartFile类型的参数
2. RequestParamMapMethodArgumentResolver
支持带有@RequestParam注解的参数 && @RequestParam注解的属性value存在 && 参数类型是实现Map接口的属性
3. PathVariableMethodArgumentResolver
支持带有@PathVariable注解的参数 且如果参数实现了Map接口,@PathVariable注解需带有value属性
4. MatrixVariableMethodArgumentResolver
支持带有@MatrixVariable注解的参数 且如果参数实现了Map接口,@MatrixVariable注解需带有value属性
5. RequestResponseBodyMethodProcessor
本文已分析过
6. ServletRequestMethodArgumentResolver
参数类型是实现或继承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod这些类。
(这就是为何我们在Controller中的方法里添加一个HttpServletRequest参数,Spring会为我们自动获得HttpServletRequest对象的原因)
7. ServletResponseMethodArgumentResolver
参数类型是实现或继承或是ServletResponse、OutputStream、Writer这些类
8. RedirectAttributesMethodArgumentResolver
参数是实现了RedirectAttributes接口的类
9. HttpEntityMethodProcessor
参数类型是HttpEntity
从名字我们也看的出来, 以Resolver结尾的是实现了HandlerMethodArgumentResolver接口的类,以Processor结尾的是实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的类。
在回来看看文章开头出现的3个接口调用错误
1.在我们调用使用了@RequestBody(tes/request)注解的接口时,Spring会指定RequestResponseBodyMethodProcessor进行处理,而我们由于没有指定content-type,postman里默认为multipart/form-data,而该reslover只能对application/json格式的参数进行处理,所以会报Unsupported Media Type。我们将参数改成json格式,然后设置content-type为application/json就能正常调用了。
2. test/requestParam方法以及地址https://127.0.0.1:9090/mybatis/test/requestParam?id=1&name=eragon&age=23,这个请求会找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在处理参数的时候使用request.getParameter(参数名)即request.getParameter("user")得到,很明显我们的参数传的是id=1&name=eragon&age=23。因此得到null,RequestParamMethodArgumentResolver处理missing value会触发MissingServletRequestParameterException异常。
3.test/date方法中,会调用RequestParamMethodArgumentResolver进行处理,同样spring会加载两个,一个useDefaultResolution的值为true,一个为false。因为是Date类型,会通过request.getParameter("date")获得参数值,然后通过DataBinder找到对应的converter去处理(如果没有对应类型自定义CustomDateEditor),默认是ObjectToObject处理sourceType=String,targetType=Date类型的转换。由于该转换器并不支持yyyy-MM-dd的时间转换,所以会报IllegalArgumentException。解决方法有几种:
1)将前段传值改为UTC标准时间格式?date=Sat, 17 May 2014 16:30:00 GMT
2)自定义日期类转换器
@Configuration
public class DateConvertConfig {
@Bean
public DateConverter getDateConverter(){
return new DateConverter();
}
}
public class DateConverter implements Converter<String,Date> {
@Override
public Date convert(String s) {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
3) 自定义属性编辑器
@InitBinder
public void dateBind(WebDataBinder binder){
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(sf, false));
}
总结
在使用SpringMvc时,接口中的入参和返回值可以选择不同的类型以及使用Spring提供的注解,本文就是探究一下不同类型的入参和返回值是如何被Spring注入和返回的。