SpringMVC中Controller
详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
目录
前言
SpringMVC是目前主流的Web MVC框架之一。
如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html
SpringMVC中Controller的方法参数可以是Integer,Double,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非常灵活。本文将分析SpringMVC是如何对这些参数进行处理的,使读者能够处理自定义的一些参数。
现象
本文使用的demo基于maven。我们先来看一看对应的现象。
@Controller
@RequestMapping(value = "/test")
public class TestController {
@RequestMapping("/testRb")
@ResponseBody
public Employee testRb(@RequestBody Employee e) {
return e;
}
@RequestMapping("/testCustomObj")
@ResponseBody
public Employee testCustomObj(Employee e) {
return e;
}
@RequestMapping("/testCustomObjWithRp")
@ResponseBody
public Employee testCustomObjWithRp(@RequestParam Employee e) {
return e;
}
@RequestMapping("/testDate")
@ResponseBody
public Date testDate(Date date) {
return date;
}
}
首先这是一个Controller,有4个方法。他们对应的参数分别是带有@RequestBody的自定义对象、自定义对象、带有@RequestParam的自定义对象、日期对象。
接下来我们一个一个方法进行访问看对应的现象是如何的。
首先第一个testRb:

第二个testCustomObj:

第三个testCustomObjWithRp:

第四个testDate:

为何返回的Employee对象会被自动解析为xml,请看楼主的另一篇博客:戳我
为何Employee参数会被解析,带有@RequestParam的Employee参数不会被解析,甚至报错?
为何日期类型不能被解析?
SpringMVC到底是如何处理这些方法的参数的?
@RequestBody、@RequestParam这两个注解有什么区别?
带着这几个问题。我们开始进行分析。
源码分析
本文所分析的源码是Spring版本4.0.2
在分析源码之前,首先让我们来看下SpringMVC中两个重要的接口。
两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler,这两个接口都是Spring3.1版本之后加入的。

SpringMVC处理请求大致是这样的:
首先被DispatcherServlet截获,DispatcherServlet通过handlerMapping获得HandlerExecutionChain,然后获得HandlerAdapter。
HandlerAdapter在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethod进行处理,ServletInvocableHandlerMethod在进行处理的时候,会分两部分别对请求跟响应进行处理。
之后HandlerAdapter得到ModelAndView,然后做相应的处理。
本文将重点介绍ServletInvocableHandlerMethod对请求以及响应的处理。

1. 处理请求的时候,会根据ServletInvocableHandlerMethod的属性argumentResolvers(这个属性是它的父类InvocableHandlerMethod中定义的)进行处理,其中argumentResolvers属性是一个HandlerMethodArgumentResolverComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodArgumentResolver接口的类,里面有各种实现了HandlerMethodArgumentResolver的List集合。

2. 处理响应的时候,会根据ServletInvocableHandlerMethod的属性returnValueHandlers(自身属性)进行处理,returnValueHandlers属性是一个HandlerMethodReturnValueHandlerComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodReturnValueHandler接口的类,里面有各种实现了HandlerMethodReturnValueHandler的List集合。

ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers这两个属性都是在ServletInvocableHandlerMethod进行实例化的时候被赋值的(使用RequestMappingHandlerAdapter的属性进行赋值)。

RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers这两个属性是在RequestMappingHandlerAdapter进行实例化的时候被Spring容器注入的。

其中默认的ArgumentResolvers:

默认的returnValueHandlers:

我们在json、xml自动转换那篇文章中已经了解,使用@ResponseBody注解的话最终返回值会被RequestResponseBodyMethodProcessor这个HandlerMethodReturnValueHandler实现类处理。
我们通过源码发现,RequestResponseBodyMethodProcessor这个类其实同时实现了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver这两个接口。

RequestResponseBodyMethodProcessor支持的请求类型是Controller方法参数中带有@RequestBody注解,支持的响应类型是Controller方法带有@ResponseBody注解。

RequestResponseBodyMethodProcessor响应的具体处理是使用消息转换器。

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

然后会执行父类(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。

下面来我们来看看常用的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的类。
下面来我们来看看常用的HandlerMethodReturnValueHandler实现类。
1. ModelAndViewMethodReturnValueHandler
返回值类型是ModelAndView或其子类
2. ModelMethodProcessor
返回值类型是Model或其子类
3. ViewMethodReturnValueHandler
返回值类型是View或其子类
4. HttpHeadersReturnValueHandler
返回值类型是HttpHeaders或其子类
5. ModelAttributeMethodProcessor
返回值有@ModelAttribute注解
6. ViewNameMethodReturnValueHandler
返回值是void或String
其余没讲过的读者可自行查看源码。
下面开始解释为何本文开头出现那些现象的原因:
1. 第一个方法testRb以及地址 http://localhost:8888/SpringMVCDemo/test/testRb?name=1&age=3
这个方法的参数使用了@RequestBody,之前已经分析过,被RequestResponseBodyMethodProcessor进行处理。之后根据http请求头部的contentType然后选择合适的消息转换器进行读取。
很明显,我们的消息转换器只有默认的那些跟部分json以及xml转换器,且传递的参数name=1&age=3,传递的头部中没有content-type,默认使用了application/octet-stream,因此触发了HttpMediaTypeNotSupportedException异常
解放方案: 我们将传递数据改成json,同时http请求的Content-Type改成application/json即可。



完美解决。
2. testCustomObj方法以及地址 http://localhost:8888/SpringMVCDemo/test/testCustomObj?name=1&age=3
这个请求会找到ServletModelAttributeMethodProcessor这个resolver。默认的resolver中有两个ServletModelAttributeMethodProcessor,只不过实例化的时候属性annotationNotRequired一个为true,1个为false。这个ServletModelAttributeMethodProcessor处理参数支持@ModelAttribute注解,annotationNotRequired属性为true的话,参数不是简单类型就通过,因此选择了ServletModelAttributeMethodProcessor,最终通过DataBinder实例化Employee对象,并写入对应的属性。
3. testCustomObjWithRp方法以及地址 http://localhost:8888/SpringMVCDemo/test/testCustomObjWithRp?name=1&age=3
这个请求会找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在处理参数的时候使用request.getParameter(参数名)即request.getParameter("e")得到,很明显我们的参数传的是name=1&age=3。因此得到null,RequestParamMethodArgumentResolver处理missing value会触发MissingServletRequestParameterException异常。 [粗略讲下,有兴趣的读者请自行查看源码]
解决方案:去掉@RequestParam注解,让ServletModelAttributeMethodProcessor来处理。
4. testDate方法以及地址 http://localhost:8888/SpringMVCDemo/test/testDate?date=2014-05-15
这个请求会找到RequestParamMethodArgumentResolver。因为这个方法与第二个方法一样,有两个RequestParamMethodArgumentResolver,属性useDefaultResolution不同。RequestParamMethodArgumentResolver支持简单类型,ServletModelAttributeMethodProcessor是支持非简单类型。最终步骤跟第三个方法一样,我们的参数名是date,于是通过request.getParameter("date")找到date字符串(这里参数名如果不是date,那么最终页面是空白的,因为没有@RequestParam注解,参数不是必须的,RequestParamMethodArgumentResolver处理null值返回null)。最后通过DataBinder找到合适的属性编辑器进行类型转换。最终找到java.util.Date对象的构造函数 public Date(String s),由于我们传递的格式不是标准的UTC时间格式,因此最终触发了IllegalArgumentException异常。
解决方案:
1. 传递参数的格式修改成标准的UTC时间格式:http://localhost:8888/SpringMVCDemo/test/testDate?date=Sat, 17 May 2014 16:30:00 GMT

2.在Controller中加入自定义属性编辑器。
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
这个@InitBinder注解在实例化ServletInvocableHandlerMethod的时候被注入到WebDataBinderFactory中的,而WebDataBinderFactory是ServletInvocableHandlerMethod的一个属性。在RequestMappingHandlerAdapter源码的803行getDataBinderFactory就是得到的WebDataBinderFactory。

之后RequestParamMethodArgumentResolver通过WebDataBinderFactory创建的WebDataBinder里的自定义属性编辑器找到合适的属性编辑器(我们自定义的属性编辑器是用CustomDateEditor处理Date对象,而testDate的参数刚好是Date),最终CustomDateEditor把这个String对象转换成Date对象。
编写自定义的HandlerMethodArgumentResolver
通过前面的分析,我们明白了SpringMVC处理Controller中的方法的参数流程。
现在,如果方法中有两个参数,且都是自定义类参数,那该如何处理呢?
@RequestMapping("/save")
public ModelAndView saveAll(@FormModel Employee employee, @FormModel Dept dept, ModelAndView view) {
view.setViewName("test/success");
view.addObject("employee", employee);
view.addObject("dept", dept);
return view;
}
我们就来试试吧。
很明显,要处理这个只能自己实现一个实现HandlerMethodArgumentResolver的类。
楼主参考了该博客中的实现。 关于实现的细节,将在之后的博客中详细介绍。
结果如下:

总结
写了这么多,主要还是巩固一下自己对SpringMVC对请求及响应的处理做一个细节的总结吧,不知道大家有没有清楚这个过程。
想熟悉这部分内容最主要的还是要熟悉HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler这两个接口以及属性编辑器、数据绑定机制。
本文难免有错误,希望读者能指出来。
参考资料
SpringMVC中Controller的更多相关文章
- springmvc 中controller与jsp传值
参考:springmvc 中controller与jsp传值 springMVC:将controller中数据传递到jsp页面 jsp中,死活拿不到controller中的变量. 花了半天,网上列出各 ...
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...
- springMVC中controller的几种返回类型
==网文1,还不错,感觉比较老旧springMVC中controller的几种返回类型 - CSDN博客http://blog.csdn.net/qq_16071145/article/details ...
- 详解SpringMVC中Controller的方法中参数的工作原理——基于maven
转自:http://www.tuicool.com/articles/F7byQn 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...
- SpringMVC中controller返回图片(转)
本文转自:http://blog.csdn.net/u011637069/article/details/51112187 SpringMVC中controller通过返回ModelAndView然后 ...
- 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...
- SpringMVC中@Controller和@RequestMapping用法和其他常用注解
一.简介 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Mo ...
- 详解SpringMVC中Controller的方法中参数的工作原理
Spring MVC中Controller的处理方法的参数可以是Integer,String,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非 ...
- springmvc中Controller方法的返回值
1.1 返回ModelAndView controller方法中定义ModelAndView对象并返回,对象中可添加model数据.指定view. 1.2 返回void 在controller方法形参 ...
随机推荐
- 【Hibernate步步为营】--复合主键映射具体解释
上篇文章讨论了继承映射,它是对象模型中最主要的特性,对于继承映射它的主要区分是字段类型的不同,所以在生成表结构时须要有新列来标识数据的类型,能够使用<subclass>标签并在标签中加入d ...
- RPM安装包-Spec文件參数具体解释与演示样例分析
spec文件是整个RPM包建立过程的中心,它的作用就如同编译程序时的Makefile文件. 1.Spec文件參数 spec文件包括建立一个RPM包必需的信息,包括哪些文件是包的一部分以及它们安装在哪个 ...
- Lenovo E46A-Win 7_无线灯亮但无法启动(耽误3天以上您信吗.....)问题: wlan autoconfig 依赖服务或组无法启动
Lenovo E46A-Win 7_无线灯亮但无法启动(耽误3天以上您信吗.....)问题: wlan autoconfig 依赖服务或组无法启动 提示: windows7 无线连接服务wlan au ...
- 分析RAC下一个SPFILE整合的三篇文章的文件更改
大约RAC下一个spfile分析_整理在_2014.4.17 说明:文章来源于网络 第一篇:RAC下SPFILE文件改动 在RAC下spfile位置的改动与单节点环境不全然一致,有些地方须要特别注意, ...
- 堆C数组实现
堆栈是一个最后出来该数据结构. 栈的基本操作包含:入栈,出栈,初始化栈,清空栈,遍历栈. C代码例如以下: #include <stdio.h> #define MaxSize 20 ty ...
- Activity的LaunchMode情景思考
此链接:http://blog.csdn.net/xiaodongrush/article/details/28597855 1. 有哪几种类型?分别有什么用? http://developer.an ...
- C++调用一个成员函数的需求this指针的情况
1.虚成员函数,因为需要this展望虚表指针的指针 2.在数据成员的操作部件的功能 #include "stdafx.h" #include <iostream> #i ...
- oracle_体系结构图_逻辑结构图
1.oracle 的体系结构图 重要!!! 2.oracle的逻辑结构图
- 如何利用百度音乐播放器的API接口来获取高音质歌曲
第一步:在网页中打开以下网址: http://box.zhangmen.baidu.com/x?op=12&count=1&title=时间都去哪儿了$$王铮亮$$$$ 其中红色地方可 ...
- C#函数式编程-序列
C#函数式编程之序列 过了许久的时间,终于趁闲暇的时间来继续将函数式编程这个专辑连载下去,这段时间开头是为IOS这个新方向做准备,将OC的教程写成了SWIFT版,当然我个人是支持Xamarin,但是我 ...