简介

SpringMvc的方法里我们可以接受各式各样类型的参数,Stirng、Integer、@RequestBody(Json)、ModelAndView(Spring自动注入的一些参数)等,那么SpringMvc是如何将这些参数注入的呢?

例子

spring query 参数丢失_spring query 参数丢失

分别用postman访问上面4个接口

spring query 参数丢失_接口调用_02

spring query 参数丢失_接口调用_03

spring query 参数丢失_参数类型_04

spring query 参数丢失_参数类型_05

除了第一个接口调用成功,另外三个接口均报错了。为什么会报错呢?@RequestBody、@RequestParam应该在什么情况下使用呢?为什日期格式无法转换呢?

SpringMvc源码

两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler,这两个接口都是Spring3.1版本之后加入的。

SpringMvc请求的入口是DispatcherServlet

spring query 参数丢失_参数类型_06

spring query 参数丢失_json格式_07

进入到父类AbstractHandlerMethodAdapter中

spring query 参数丢失_json格式_08

进入到RequestMappingHandlerAdapter类中跟进handleInternal方法

spring query 参数丢失_json格式_09

spring query 参数丢失_接口调用_10

进入到ServletInvocableHandlerMethod类

spring query 参数丢失_json格式_11

进入InvocableHandlerMethod类

spring query 参数丢失_spring query 参数丢失_12

spring query 参数丢失_spring query 参数丢失_13

进入HandlerMethodArgumentResolverComposite类

spring query 参数丢失_json格式_14

spring query 参数丢失_spring query 参数丢失_15

methodArgumentResolver.supportsParameter(parameter) 这个方法就是判断当前遍历的resolver和参数是不是匹配,不通的reslover都有自己的实现。当找到对应的resovler后,就用resolver去处理参数了。我们再回到之前的代码。

spring query 参数丢失_参数类型_16

我们通过源码发现,RequestResponseBodyMethodProcessor这个类其实同时实现了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver这两个接口。所以这里实际上调用是该类的方法,我们继续跟进。

spring query 参数丢失_接口调用_17

处理请求的时候使用内部的readWithMessageConverters方法。

spring query 参数丢失_spring query 参数丢失_18

spring query 参数丢失_参数类型_19

spring query 参数丢失_spring query 参数丢失_20

到这里处理入参的流程就结束了,总结一下就是先找到对应的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));
    }

 

spring query 参数丢失_json格式_21

总结

在使用SpringMvc时,接口中的入参和返回值可以选择不同的类型以及使用Spring提供的注解,本文就是探究一下不同类型的入参和返回值是如何被Spring注入和返回的。