热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

你想过Controller这些方法里的参数是如何工作的吗?

点击上方“方志朋”,选择“设为星标”回复”666“获取新整理的面试文章前言SpringMVC是目前主流的WebMVC框架之一。SpringMVC中Controller

点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

前言

SpringMVC是目前主流的Web MVC框架之一。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中两个重要的接口。

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

 使用@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 {@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(FormObj.class);}@Override
public 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 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

作者:format

https://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html

热门内容:项目实践:SpringBoot三招组合拳,手把手教你打出优雅的后端接口
一次SQL查询优化原理分析
start.aliyun.com正式上线
惊呆了,Spring Boot居然这么耗内存!
两小时入门Docker最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・


推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
author-avatar
小子转过来_406
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有