背景前段时间开发一个接口,因为调用我接口的同事脾气特别好,我也就不客气,我就直接把源代码发给他当接口定义了。

没想到同事看到我的代码问:要么 get  a,b,c  要么  post [a,b,c]。这么写可以自动解析?他们一直都是自己转换成list。

我很肯定的说可以,但是已经习惯这么用了,没有了解底层的机制,这里其实RequestParam这个注解是不能省略的,普通的字符串参数可以自动绑定,需要这种内部转换的不可以。
参数绑定原理
Spring的参数解析使用HandlerMethodArgmentResolver类型的组件完成。不同类型的使用不同的ArgumentResolver来解析。具体参考RequestMappingHandlerAdapter类的源码。里面有个方法是很好的诠释:

// 获取默认的 HandlerMethodArgumentResolver
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// 1.基于注解的参数解析 <-- 解析的数据来源主要是 HttpServletRequest | ModelAndViewContainer
// Annotation-based argument resolution
// 解析被注解 @RequestParam, @RequestPart 修饰的参数, 数据的获取通过 HttpServletRequest.getParameterValues
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
// 解析被注解 @RequestParam 修饰, 且类型是 Map 的参数, 数据的获取通过 HttpServletRequest.getParameterMap
resolvers.add(new RequestParamMapMethodArgumentResolver());
// 解析被注解 @PathVariable 修饰, 数据的获取通过 uriTemplateVars, 而 uriTemplateVars 却是通过 RequestMappingInfoHandlerMapping.handleMatch 生成, 其实就是 uri 中映射出的 key <-> value
resolvers.add(new PathVariableMethodArgumentResolver());
// 解析被注解 @PathVariable 修饰 且数据类型是 Map, 数据的获取通过 uriTemplateVars, 而 uriTemplateVars 却是通过 RequestMappingInfoHandlerMapping.handleMatch 生成, 其实就是 uri 中映射出的 key <-> value
resolvers.add(new PathVariableMapMethodArgumentResolver());
// 解析被注解 @MatrixVariable 修饰, 数据的获取通过 URI提取了;后存储的 uri template 变量值
resolvers.add(new MatrixVariableMethodArgumentResolver());
// 解析被注解 @MatrixVariable 修饰 且数据类型是 Map, 数据的获取通过 URI提取了;后存储的 uri template 变量值
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
// 解析被注解 @ModelAttribute 修饰, 且类型是 Map 的参数, 数据的获取通过 ModelAndViewContainer 获取, 通过 DataBinder 进行绑定
resolvers.add(new ServletModelAttributeMethodProcessor(false));
// 解析被注解 @RequestBody 修饰的参数, 以及被@ResponseBody修饰的返回值, 数据的获取通过 HttpServletRequest 获取, 根据 MediaType通过HttpMessageConverter转换成对应的格式, 在处理返回值时 也是通过 MediaType 选择合适HttpMessageConverter, 进行转换格式, 并输出
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
// 解析被注解 @RequestPart 修饰, 数据的获取通过 HttpServletRequest.getParts()
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
// 解析被注解 @RequestHeader 修饰, 数据的获取通过 HttpServletRequest.getHeaderValues()
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
// 解析被注解 @RequestHeader 修饰且参数类型是 Map, 数据的获取通过 HttpServletRequest.getHeaderValues()
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
// 解析被注解 @CookieValue 修饰, 数据的获取通过 HttpServletRequest.getCookies()
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
// 解析被注解 @Value 修饰, 数据在这里没有解析
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// 解析被注解 @SessionAttribute 修饰, 数据的获取通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_SESSION)
resolvers.add(new SessionAttributeMethodArgumentResolver());
// 解析被注解 @RequestAttribute 修饰, 数据的获取通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST)
resolvers.add(new RequestAttributeMethodArgumentResolver()); // 2.基于类型的参数解析器
// Type-based argument resolution
// 解析固定类型参数(比如: ServletRequest, HttpSession, InputStream 等), 参数的数据获取还是通过 HttpServletRequest
resolvers.add(new ServletRequestMethodArgumentResolver());
// 解析固定类型参数(比如: ServletResponse, OutputStream等), 参数的数据获取还是通过 HttpServletResponse
resolvers.add(new ServletResponseMethodArgumentResolver());
// 解析固定类型参数(比如: HttpEntity, RequestEntity 等), 参数的数据获取还是通过 HttpServletRequest
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
// 解析固定类型参数(比如: RedirectAttributes), 参数的数据获取还是通过 HttpServletResponse
resolvers.add(new RedirectAttributesMethodArgumentResolver());
// 解析固定类型参数(比如: Model等), 参数的数据获取通过 ModelAndViewContainer
resolvers.add(new ModelMethodProcessor());
// 解析固定类型参数(比如: Model等), 参数的数据获取通过 ModelAndViewContainer
resolvers.add(new MapMethodProcessor());
// 解析固定类型参数(比如: Errors), 参数的数据获取通过 ModelAndViewContainer
resolvers.add(new ErrorsMethodArgumentResolver());
// 解析固定类型参数(比如: SessionStatus), 参数的数据获取通过 ModelAndViewContainer
resolvers.add(new SessionStatusMethodArgumentResolver());
// 解析固定类型参数(比如: UriComponentsBuilder), 参数的数据获取通过 HttpServletRequest
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// 3.自定义参数解析器
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
//这两个解析器可以解析所有类型的参数
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
在一步步跟踪源码之后,最终在PropertyEditorRegistrySupport这个类中,有一个createDefaultEditors的私有方法。里面定义了各种类型转换:
private void createDefaultEditors() {
this.defaultEditors = new HashMap();
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
if(pathClass != null) {
this.defaultEditors.put(pathClass, new PathEditor());
} this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
if(zoneIdClass != null) {
this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());
} this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
this.defaultEditors.put(Character.TYPE, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
this.defaultEditors.put(Boolean.TYPE, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
this.defaultEditors.put(Byte.TYPE, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(Short.TYPE, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(Integer.TYPE, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(Long.TYPE, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(Float.TYPE, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(Double.TYPE, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
if(this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}

从上面的方法里就可以知道都默认支持哪些类型的自动换换了。中间过程的源码不一一贴了。

总结一下参数解析绑定的过程

1.SpringMVC初始化时,RequestMappingHanderAdapter类会把一些默认的参数解析器添加到argumentResolvers中。当SpringMVC接收到请求后首先根据url查找对应的HandlerMethod。

2.遍历HandlerMethod的MethodParameter数组。

3.根据MethodParameter的类型来查找确认使用哪个HandlerMethodArgumentResolver。

4.解析参数,从请求中解析出MethodParameter对应的参数,结果都是字符串。

5.转换参数,在DataBinder时PropertyEditorRegistrySupport把String转换成具体方法所需要的类型,这里就包括了基本类型、对象、List等。

Spring参数的自解析--还在自己转换?你out了!的更多相关文章

  1. Spring MVC Controller中解析GET方式的中文参数会乱码的问题(tomcat如何解码)

    Spring MVC Controller中解析GET方式的中文参数会乱码的问题 问题描述 在工作上使用突然出现从get获取中文参数乱码(新装机器,tomcat重新下载和配置),查了半天终于找到解决办 ...

  2. spring mvc: 参数方法名称解析器(用参数来解析控制器下的方法)MultiActionController/ParameterMethodNameResolver/ControllerClassNameHandlerMapping

    spring mvc: 参数方法名称解析器(用参数来解析控制器下的方法)MultiActionController/ParameterMethodNameResolver/ControllerClas ...

  3. Spring MVC参数方法名称解析器

    以下示例显示如何使用Spring Web MVC框架来实现多动作控制器的参数方法名称解析器. MultiActionController类可在单个控制器中分别映射多个URL到对应的方法. 所下所示配置 ...

  4. Spring MVC-控制器(Controller)-参数方法名称解析器(Parameter Method Name Resolver )示例(转载实践)

    以下内容翻译自:https://www.tutorialspoint.com/springmvc/springmvc_parametermethodnameresolver.htm 说明:示例基于Sp ...

  5. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  6. 【死磕 Spring】----- IOC 之解析 bean 标签:开启解析进程

    原文出自:http://cmsblogs.com import 标签解析完毕了,再看 Spring 中最复杂也是最重要的标签 bean 标签的解析过程. 在方法 parseDefaultElement ...

  7. [转载]SpringBoot系列: SpringMVC 参数绑定注解解析

    本文转载自 https://www.cnblogs.com/morethink/p/8028664.html, 作者写得非常好, 致谢! SpringMVC 参数绑定注解解析   本文介绍了用于参数绑 ...

  8. spring+mybaits xml配置解析----转

    一.项目中spring+mybaits xml配置解析 一般我们会在datasource.xml中进行如下配置,但是其中每个配置项原理和用途是什么,并不是那么清楚,如果不清楚的话,在使用时候就很有可能 ...

  9. Spring IOC设计原理解析:本文乃学习整理参考而来

    Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...

随机推荐

  1. 剑指offer第二版-总结:二叉树的遍历

    思想:前序(根左右),中序(左根右),后序(左右根) 前序非递归遍历: 首先判断根是否为空,将根节点入栈 1.若栈为空,则退出循环 2.将栈顶元素弹出,访问弹出的节点 3.若弹出的节点的右孩子不为空则 ...

  2. MyBatis从入门到精通:select用法进一步讲解

    selectAll:笔记 /* 定义接口方法的返回值的时候,必须注意查询SQL可能返回的结果数量.当 返回值最多只有一个结果的时候,可以将结果返回值定义为SysUser,此时 返回值类型为List&l ...

  3. 《C Primer Plus(第6版)中文版》勘误

    搬运自己2016年11月28日发布于SegmentFault的文章.链接:https://segmentfault.com/a/1190000007626460 本勘误由本人整理并发布,仅针对下方列出 ...

  4. HttpClient多文件上传代码及普通参数中文乱码问题解决

    该随笔记录了在实际项目中使用HttpClient调用外部api,需上传文件和普通参数的代码. 笔者在使用 HttpClient 调用 http api 接口时,需要服务端上传文件和一些普通参数给 ht ...

  5. 为什么选择 Spring 作为 Java 框架

    1. 概述 在本文中,我们将讨论 Spring 作为最流行的 Java 框架之一的主要价值体现. 最重要的是,我们将尝试理解 Spring 成为我们选择框架的原因.Spring 的详细信息及其组成部分 ...

  6. Windows环境下main()函数传入参数

    最近几天在写一个模仿windows自带的ping程序,也从网上找过一些源码,但大都需要向主函数main中传入参数,这里简单总结一下向主函数中传参的方法. 方法一:项目->属性->调试-&g ...

  7. linux 使用git

    linux下使用git管理代码   环境:centos7.2 :    工具:xshell 1.安装git,命令如下: #yum install git 2.设置用户名和邮箱,命令如下: #git c ...

  8. Mysql优化(出自官方文档) - 第三篇

    目录 Mysql优化(出自官方文档) - 第三篇 1 Multi-Range Read Optimization(MRR) 2 Block Nested-Loop(BNL) and Batched K ...

  9. idea新建javaweb工程

    最近尝试了idea的使用,将idea建立javaweb工程的步骤记录下来 1.方框里边是重点 2.next后输入工程文件名点击finish 3.如图看到项目文件夹里边没有WEB-INF文件夹及里边的w ...

  10. javaweb入门---web服务器与HTTP协议基础

    上文web基础简介了web到底是什么,以及身为Java开发人员需要掌握的地方.本文将解答web服务器是什么,怎么使用?还有关于http协议的基础知识. web服务器 web服务器的大概念很广泛,但是通 ...