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

没想到同事看到我的代码问:要么 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. Bzoj 3654 图样图森波 题解

    3654: 图样图森破 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 123  Solved: 66[Submit][Status][Discuss] ...

  2. 20141126-传智播客.NET3.3版

  3. [原创]自动化部署K8S(v1.10.11)集群

          标准运维实现自动化部署K8S集群主要分两步,第一步是部署gse-agent,拱第二步执行部署. 第一步:部署gse-agent.如下: 第二步:部署k8s集群.主要通过作业平台分为5小步执 ...

  4. 如何入门 MySQL

    如何入门MySQL 前言: 关于如何入门MySQL,后台有好多同学咨询我,可能部分读者刚开始学习MySQL,我前面发的文章对部分同学来说暂时接触不到.原本写技术文章的目的是记录自己的工作学习,没有考虑 ...

  5. 和朱晔一起复习Java并发(五):并发容器和同步器

    本节我们先会来复习一下java.util.concurrent下面的一些并发容器,然后再会来简单看一下各种同步器. ConcurrentHashMap和ConcurrentSkipListMap的性能 ...

  6. C#7.1 新增功能

    连载目录    [已更新最新开发文章,点击查看详细] C# 7.1 是 C# 语言的第一个点版本(更新版本). 它标志着该语言发布节奏的加速. 理想情况下,可以在每个新功能准备就绪时更快推出新功能.  ...

  7. Netty中的装饰者模式

    装饰者的应用 所谓装饰者,说白了,目的就是对现有的对象进行增强,装饰者设计模式最大的优点就是,它在扩展类原有功能的基础上还避免的类爆炸的情况 Netty中的装饰者模式的应用 ByteBuf是netty ...

  8. sessionID是如何在客户端和服务器端传递的?

    sessionID是如何在客户端和服务器端传递的? 服务器初次创建session的时候后返回session到客服端(在返回头(response)中有setCookie),浏览器会把sessionnam ...

  9. CSS布局定位基础-盒模型和定位机制

    1. 盒模型 2. 外边距合并 3. 定位机制 4. Float 5. Position:属性有哪些取值,它们的行为是什么? 无依赖绝对定位? 6. Display:常见属性值有哪些取值? 7. 对B ...

  10. Linux 下实践 VxLAN:虚拟机和 Docker 场景

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫. 在上篇文章 ...