详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] good

目录

前言

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")@ResponseBodypublic Employee testRb(@RequestBody Employee e) {return e;}@RequestMapping("/testCustomObj")@ResponseBodypublic Employee testCustomObj(Employee e) {return e;}@RequestMapping("/testCustomObjWithRp")@ResponseBodypublic Employee testCustomObjWithRp(@RequestParam Employee e) {return e;}@RequestMapping("/testDate")@ResponseBodypublic 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中两个重要的接口。

两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler,这两个接口都是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中的方法的参数流程。

现在,如果方法中有两个参数,且都是自定义类参数,那该如何处理呢?

很明显,要处理这个只能自己实现一个实现HandlerMethodArgumentResolver的类。

先定义1个注解FormObj:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FormObj {//参数别名String value() default "";//是否展示, 默认展示boolean show() default true;
}

 

 

然后是HandlerMethodArgumentResolver:

public class FormObjArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(FormObj.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {FormObj formObj = parameter.getParameterAnnotation(FormObj.class);String alias = getAlias(formObj, parameter);//拿到obj, 先从ModelAndViewContainer中拿,若没有则new1个参数类型的实例Object obj = (mavContainer.containsAttribute(alias)) ?mavContainer.getModel().get(alias) : createAttribute(alias, parameter, binderFactory, webRequest);//获得WebDataBinder,这里的具体WebDataBinder是ExtendedServletRequestDataBinderWebDataBinder binder = binderFactory.createBinder(webRequest, obj, alias);Object target = binder.getTarget();if(target != null) {//绑定参数
            bindParameters(webRequest, binder, alias);//JSR303 验证
            validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors()) {if (isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}}if(formObj.show()) {mavContainer.addAttribute(alias, target);}return target;}private Object createAttribute(String alias, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) {return BeanUtils.instantiateClass(parameter.getParameterType());}private void bindParameters(NativeWebRequest request, WebDataBinder binder, String alias) {ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);MockHttpServletRequest newRequest = new MockHttpServletRequest();Enumeration<String> enu = servletRequest.getParameterNames();while(enu.hasMoreElements()) {String paramName = enu.nextElement();if(paramName.startsWith(alias)) {newRequest.setParameter(paramName.substring(alias.length()+1), request.getParameter(paramName));}}((ExtendedServletRequestDataBinder)binder).bind(newRequest);}protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {Annotation[] annotations = parameter.getParameterAnnotations();for (Annotation annot : annotations) {if (annot.annotationType().getSimpleName().startsWith("Valid")) {Object hints = AnnotationUtils.getValue(annot);binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});break;}}}protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {int i = parameter.getParameterIndex();Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));return !hasBindingResult;}private String getAlias(FormObj formObj, MethodParameter parameter) {//得到FormObj的属性value,也就是对象参数的简称String alias = formObj.value();if(alias == null || StringUtils.isBlank(alias)) {//如果简称为空,取对象简称的首字母小写开头String simpleName = parameter.getParameterType().getSimpleName();alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);}return alias;}}

 

 

对应Controller:

@Controller
@RequestMapping(value = "/foc")
public class FormObjController {@RequestMapping("/test1")public String test1(@FormObj Dept dept, @FormObj Employee emp) {return "index";}@RequestMapping("/test2")public String test2(@FormObj("d") Dept dept, @FormObj("e") Employee emp) {return "index";}@RequestMapping("/test3")public String test3(@FormObj(value = "d", show = false) Dept dept, @FormObj("e") Employee emp) {return "index";}}
 

结果如下:

总结

写了这么多,主要还是巩固一下自己对SpringMVC对请求及响应的处理做一个细节的总结吧,不知道大家有没有清楚这个过程。

想熟悉这部分内容最主要的还是要熟悉HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler这两个接口以及属性编辑器、数据绑定机制。

本文难免有错误,希望读者能指出来。

参考资料

http://www.iteye.com/topic/1127676

http://jinnianshilongnian.iteye.com/blog/1717180

[http://www.tuicool.com/articles/F7byQn

http://www.2cto.com/kf/201405/301660.html]

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对不起,我不是一个自律的人

大家好&#xff0c;我是 &#x1f41f;&#x1f4a8;。前天&#xff0c;星球 的一位大学生朋友问了我几个问题&#xff1a;你大学时如何安排每日的时间&#xff1f;为什么能学那么多技术&#xff1f;你会学习到很晚吗&#xff1f;你是如何保持自律的&#xff1f;我觉得这几个问…

保证接口数据安全的10种方案

前言 大家好&#xff0c;我是程序汪&#xff0c;互联网项目需要特别注意数据安全&#xff0c;如果你简历上是互联网类型项目&#xff0c;安全方面肯定要能说出个一二三&#xff0c;下面分享下这方面的干货&#xff0c;大家可以记住几条&#xff0c;面试时好说道说道 我们日常…

Html5本地存储LocalStorage

HTML5 提供了两种在客户端存储数据的新方法&#xff1a; localStorage - 没有时间限制的数据存储sessionStorage - 针对一个 session 的数据存储在浏览器中打开审查元素&#xff08;如谷歌F12&#xff09;&#xff0c;在Resources下面可以查看里面的数据。 localStorage提供了几…

java第一季2.2

2019独角兽企业重金招聘Python工程师标准>>> 标识符&#xff1a; 是给变量类方法命名的符号、标识符开头可以_、字母、$命名&#xff0c;不可以用数字命名。关键字不可命名&#xff0c;大小写区分。不可以用非法字符 变量&#xff1a;变量类型。变量名。变量值。如&…

读《华为数字化转型之道》

数字化转型应该很多人都听过&#xff0c;但如果你做过 ToB 软件&#xff0c;听得更多的是信息化&#xff0c;那信息化和数字化是什么关系呢&#xff1f;下面用一个小例子来说说我的理解。记得刚上初中的时候&#xff0c;平时测验、考试的试卷&#xff0c;都是人工在板上进行刻写…

Thrift基本原理及使用

参考文章RPC 基本原理与 Apach Thrift 初体验 RPC基本原理 RPC(Remote Procedure Call)&#xff0c;远程过程调用&#xff0c;大部分的RPC框架都遵循如下三个开发步骤&#xff1a; 1. 定义一个接口说明文件&#xff1a;描述了对象(结构体)、对象成员、接口方法等一系列信息&am…

01-H5语义化标签

转载于:https://www.cnblogs.com/Zeki/p/5901399.html

配置中心 App Configuration (三):配置的动态更新

Get Azure key-value pairs from App configuration | Serverless360写在前面我在前文&#xff1a;《微软Azure配置中心 App Configuration (一)&#xff1a;轻松集成到Asp.Net Core》已经介绍了Asp.net Core怎么轻易的接入azure 配置中心App Configuration(下称azure 配置中心…

万字总结 JS 数据结构与常用的算法

前言 首先&#xff0c;为什么我会学习数据结构与算法呢&#xff0c;其实主要是有两方面 第一&#xff0c;是我在今年的flag里明确说到我会学这个东西第二&#xff0c;学了这些&#xff0c;对自己以后在工作或者面试也会带来许多好处然后&#xff0c;本文是最近学习的一个总结文…

精通Java设计模式从初见到相爱之工厂+策略模式(3)

为什么80%的码农都做不了架构师&#xff1f;>>> 1、公司项目需求。 用户签到活动&#xff0c;会员签到怎么处理&#xff0c;超级会员怎么处理&#xff0c;普通用户签到怎么处理&#xff0c;针对不同的档次&#xff0c;有不同的方案&#xff0c;所以在项目中用到了策…

jquery weui 中alert弹出框在ios中跳动问题

问题描述&#xff1a; jquery-weui中的弹出框在ios上会有一个右下角向中间滑动的效果&#xff0c;在Android上没有这个效果。 解决方法&#xff1a; 修该jquery-weui.js中的openModal方法如下图: 转载于:https://www.cnblogs.com/xianZJ/p/6773097.html

WPF效果第一百九十五篇之又玩ListBox

ListBox一直是我的最爱;今天再次基于他玩耍一下不一样的效果;闲话不多扯直接看效果:1、这次直接用的ItemContainerStyle:2、通过HitTest实现点选边框&#xff1a;Point point e.GetPosition(LightDarkListBox); VisualTreeHelper.HitTest(LightDarkListBox, new HitTestFilter…

Web3,互联网新造神“机器”?

本文来自微信公众号&#xff1a;每经头条 &#xff08;ID&#xff1a;nbdtoutiao&#xff09;&#xff0c;作者&#xff1a;李蕾&#xff0c;编辑&#xff1a;肖芮冬&#xff0c;头图来自&#xff1a;视觉中国 “与目前的互联网相比&#xff0c;Web3基于区块链等底层技术&#…

普通中年人的真实出路

阅读本文大概需要6分钟。互联网人甚至中国整体的用工市场的确有中年淘汰的问题&#xff0c;我们可以当它不存在&#xff0c;甚至当有人给出解法的时候&#xff0c;我们也可以认为他们在传播焦虑&#xff0c;但事实就是事实&#xff0c;它的存在不随个人意愿而转移。最近抖音上有…

项目管理常见的问题

综合管理 缺乏企业级的项目管理平台;项目目标不清楚;项目经理不了解项目管理流程和工具;项目模板不统一;计划意识薄弱&#xff0c;缺乏规范的分解。难以过程监控&#xff0c;实时地了解项目进度,靠手工统计和汇报项目进度&#xff0c;难以真实反映进度。项目控制不力&#xff0…

【温故知新】C# Linq中 Select SelectMany 使用技巧

微信公众号&#xff1a;趣编程ACE关注可了解更多的.NET日常实战开发技巧&#xff0c;如需源码 后台回复 源码 即可;如果觉得对你有帮助&#xff0c;欢迎关注C# Linq中 Select && SelectMany 使用技巧Select 和 SelectMany 是我们开发中对集合常用的两个扩展方法&#x…

bzoj4870

http://www.lydsy.com/JudgeOnline/problem.php?id4870 矩阵快速幂。。。 人话题意&#xff1a;从nk个物品里选模k余r个物品&#xff0c;问方案数模P 那么我们有方程 f[i][j]f[i-1][j]f[i-1][j-1] 跟组合数一个样子 j∈(0,k) 这个物品选还是不选加起来 构造矩阵&#xff1a;x.…

Codeforces Round #410 (Div. 2) D. Mike and distribution 思维+数学

链接&#xff1a; http://codeforces.com/contest/798/problem/D 题意&#xff1a; 给你两个长度为n的数列a和b&#xff0c;让你选n/21个下标&#xff0c;使得2*∑ai>suma,2*∑bi>sumb 题解1&#xff1a; 用一个叫random_shuffle的东西&#xff0c;每次都乱选&#xff0c…

PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏

一&#xff1a;背景 上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏&#xff0c;这种内存泄漏往往是用 C 的 malloc 或者 C 的 new 分配而不释放所造成的&#xff0c;这一篇我们来聊一下由 VirtualAlloc 方法造成的泄漏如何去甄别&#xff1f;了解 VirtualAlloc 的…

[APP]- 找回Xcode7的代码折叠功能

为什么80%的码农都做不了架构师&#xff1f;>>> 原 找回Xcode7的代码折叠功能 升级到Xcode7后&#xff0c;会发现代码折叠功能不见了&#xff0c;这是怎么回事&#xff1f; 其实这个功能还在的&#xff0c;只是苹果默认把这个功能禁掉了&#xff1a;在Xcode菜单里选…