一、问题

springMVC对于下面这种接口,参数是怎么解析的:

@GetMapping("/hello/{id}")
public void hello3(@PathVariable Long id) {System.out.println("id = " + id);
}

这是我们日常中最常见的参数定义方式,相信很多小伙伴对此很感兴趣。由于这块涉及到一个非常庞大的类AbstractNamedValueMethodArgumentResolver,因此这里我单独写了一篇文章来和大家分享这个问题。在正式分享之前,我们先来整体看看参数解析器都有哪些。

二、参数解析器

HandlerMethodArgumentResolver 就是我们口口声声说的参数解析器,它的实现类还是蛮多的,因为每一种类型的参数都对应了一个参数解析器

为了理解方便,我们可以将这些参数解析器分为四大类:

  • xxxMethodArgumentResolver:这就是一个普通的参数解析器。
  • xxxMethodProcessor:不仅可以当作参数解析器,还可以处理对应类型的返回值。
  • xxxAdapter:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器
  • HandlerMethodArgumentResolverComposite:这个看名字就知道是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器

大致上可以分为这四类,其中最重要的当然就是前两种了。springMVC中有26中参数解析器

三、参数解析器概览

接下来我们来先来大概看看这些参数解析器分别都是用来干什么的。

  1. MapMethodProcessor

这个用来处理 Map/ModelMap 类型的参数,解析完成后返回 model。

  1. PathVariableMethodArgumentResolver

这个用来处理使用了 @PathVariable 注解并且参数类型不为 Map 的参数,参数类型为 Map 则使用 PathVariableMapMethodArgumentResolver 来处理。

  1. PathVariableMapMethodArgumentResolver

见上。

  1. ErrorsMethodArgumentResolver

这个用来处理 Error 参数,例如我们做参数校验时的 BindingResult

  1. AbstractNamedValueMethodArgumentResolver

这个用来处理 key/value 类型的参数,如请求头参数、使用了 @PathVariable 注解的参数以及 Cookie 等

  1. RequestHeaderMethodArgumentResolver

这个用来处理使用了 @RequestHeader 注解,并且参数类型不是 Map 的参数(参数类型是 Map 的使用 RequestHeaderMapMethodArgumentResolver)。

  1. RequestHeaderMapMethodArgumentResolver

见上。

  1. RequestAttributeMethodArgumentResolver

这个用来处理使用了 @RequestAttribute 注解的参数

  1. RequestParamMethodArgumentResolver

这个功能就比较广了。使用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理。需要注意的是,如果 @RequestParam 注解的参数类型是 Map,则该注解必须有 name 值,否则解析将由 RequestParamMapMethodArgumentResolver 完成。

  1. RequestParamMapMethodArgumentResolver

见上。

  1. AbstractCookieValueMethodArgumentResolver

这个是一个父类,处理使用了 @CookieValue 注解的参数

  1. ServletCookieValueMethodArgumentResolver

这个处理使用了 @CookieValue 注解的参数

  1. MatrixVariableMethodArgumentResolver

这个处理使用了 @MatrixVariable 注解并且参数类型不是 Map 的参数,如果参数类型是 Map,则使用 MatrixVariableMapMethodArgumentResolver 来处理。

  1. MatrixVariableMapMethodArgumentResolver

见上。

  1. SessionAttributeMethodArgumentResolver

这个用来处理使用了 @SessionAttribute 注解的参数

  1. ExpressionValueMethodArgumentResolver

这个用来处理使用了 @Value 注解的参数

  1. ServletResponseMethodArgumentResolver

这个用来处理 ServletResponse、OutputStream 以及 Writer 类型的参数

  1. ModelMethodProcessor

这个用来处理 Model 类型参数,并返回 model。

  1. ModelAttributeMethodProcessor

这个用来处理使用了 @ModelAttribute 注解的参数

  1. SessionStatusMethodArgumentResolver

这个用来处理 SessionStatus 类型的参数。

  1. PrincipalMethodArgumentResolver

这个用来处理 Principal 类型参数

  1. AbstractMessageConverterMethodArgumentResolver

这是一个父类,当使用 HttpMessageConverter 解析 requestbody 类型参数时,相关的处理类都会继承自它。

  1. RequestPartMethodArgumentResolver

这个用来处理使用了 @RequestPart 注解、MultipartFile 以及 Part 类型的参数。

  1. AbstractMessageConverterMethodProcessor

这是一个工具类,不承担参数解析任务。

  1. RequestResponseBodyMethodProcessor

这个用来处理添加了 @RequestBody 注解的参数

  1. HttpEntityMethodProcessor

这个用来处理 HttpEntity 和 RequestEntity 类型的参数。

  1. ContinuationHandlerMethodArgumentResolver

  2. AbstractWebArgumentResolverAdapter

这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。

  1. ServletWebArgumentResolverAdapter

这个给父类提供 request。

  1. UriComponentsBuilderMethodArgumentResolver

这个用来处理 UriComponentsBuilder 类型的参数

  1. ServletRequestMethodArgumentResolver

这个用来处理 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数

  1. HandlerMethodArgumentResolverComposite

这个看名字就知道是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。

  1. RedirectAttributesMethodArgumentResolver

这个用来处理 RedirectAttributes 类型的参数

好了,各个参数解析器的大致功能就给大家介绍完了,接下来我们选择其中一种,来具体说说它的源码。

四、以AbstractNamedValueMethodArgumentResolver为例解析

AbstractNamedValueMethodArgumentResolver 是一个抽象类,一些键值对类型的参数解析器都是通过继承它实现的,它里边定义了很多这些键值对类型参数解析器的公共操作。

AbstractNamedValueMethodArgumentResolver 中也是应用了很多模版模式,例如它没有实现 supportsParameter 方法,该方法的具体实现在不同的子类中,resolveArgument 方法它倒是实现了,我们一起来看下:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}// Check for null value after conversion of incoming argument valueif (arg == null && namedValueInfo.defaultValue == null &&namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;
}
  1. 首先根据当前请求获取一个 NamedValueInfo 对象,这个对象中保存了参数的三个属性:参数名、参数是否必须以及参数默认值。具体的获取过程就是先去缓存中拿,缓存中如果有,就直接返回,缓存中如果没有,则调用 createNamedValueInfo 方法去创建,将创建结果缓存起来并返回。createNamedValueInfo 方法是一个模版方法,具体的实现在子类中。
  2. 接下来处理 Optional 类型参数。
  3. resolveEmbeddedValuesAndExpressions 方法是为了处理注解中使用了 SpEL 表达式的情况,例如如下接口:
@GetMapping("/hello2")
public void hello2(@RequestParam(value = "${aa.bb}") String name) {System.out.println("name = " + name);
}

参数名使用了表达式,那么 resolveEmbeddedValuesAndExpressions 方法的目的就是解析出表达式的值,如果没用到表达式,那么该方法会将原参数原封不动返回。

  1. 接下来调用 resolveName 方法解析出参数的具体值,这个方法也是一个模版方法,具体的实现在子类中。
  2. 如果获取到的参数值为 null,先去看注解中有没有默认值,然后再去看参数值是否是必须的,如果是,则抛异常出来,否则就设置为 null 即可。
  3. 如果解析出来的参数值为空字符串 “”,则也去 resolveEmbeddedValuesAndExpressions 方法中走一遭。
  4. 最后则是 WebDataBinder 的处理,解决一些全局参数的问题,WebDataBinder 松哥在之前的文章中也有介绍过,传送门: @ControllerAdvice 的三种使用场景。

大致的流程就是这样。

在这个流程中,我们看到主要有如下两个方法是在子类中实现的:

  • createNamedValueInfo
  • resolveName

在加上 supportsParameter 方法,子类中一共有三个方法需要我们重点分析。那么接下来我们就以 RequestParamMethodArgumentResolver 为例,来看下这三个方法。

五、RequestParamMethodArgumentResolver

  1. supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {if (parameter.hasParameterAnnotation(RequestParam.class)) {if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && StringUtils.hasText(requestParam.name()));}else {return true;}}else {if (parameter.hasParameterAnnotation(RequestPart.class)) {return false;}parameter = parameter.nestedIfOptional();if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {return true;}else if (this.useDefaultResolution) {return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());}else {return false;}}
}
public static boolean isSimpleProperty(Class<?> type) {return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||CharSequence.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) ||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type ||Locale.class == type ||Class.class == type));
}

从 supportsParameter 方法中可以非常方便的看出支持的参数类型:

  1. 首先参数如果有 @RequestParam 注解的话,则分两种情况:参数类型如果是 Map,则 @RequestParam 注解必须配置name 属性,否则不支持;如果参数类型不是 Map,则直接返回 true,表示总是支持(想想自己平时使用的时候是不是这样)。
  2. 参数如果含有@RequestPart 注解,则不支持。
  3. 检查是不是文件上传请求,如果是,返回 true 表示支持
  4. 如果前面都没能返回,则使用默认的解决方案,判断是不是简单类型,主要就是 Void、枚举、字符串、数字、日期等等。

这块代码其实很简单,支持谁不支持谁,一目了然。

  1. createNamedValueInfo
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
//读取注解中的属性,构造 RequestParamNamedValueInfo 对象返回RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {public RequestParamNamedValueInfo() {super("", false, ValueConstants.DEFAULT_NONE);}public RequestParamNamedValueInfo(RequestParam annotation) {super(annotation.name(), annotation.required(), annotation.defaultValue());}
}

获取注解,读取注解中的属性,构造 RequestParamNamedValueInfo 对象返回。

  1. resolveName
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);if (servletRequest != null) {Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {return mpArg;}}Object arg = null;MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);if (multipartRequest != null) {List<MultipartFile> files = multipartRequest.getFiles(name);if (!files.isEmpty()) {arg = (files.size() == 1 ? files.get(0) : files);}}if (arg == null) {String[] paramValues = request.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}}return arg;
}

这个方法思路也比较清晰:

  1. 前面两个 if 主要是为了处理文件上传请求。
  2. 如果不是文件上传请求,则调用 request.getParameterValues方法取出参数返回即可。

文章转自

SpringMVC 参数解析器相关推荐

  1. springmvc自定义参数解析器

    由于开发中一般使用参数提交方式是json格式,对于单个参数的传递使用无法接收只能自定义参数解析器处理 springmvc的自定义参数解析器实现HandlerMethodArgumentResolver ...

  2. springmvc自定义参数解析器/类型转换器

    概述 有些时候我们需要对GET请求的入参做自定义的处理,比较常见的就是字符串反序列化时间类型了,常用的像@DateTimeFormat注解,但是这需要在每个入参的属性上都加上这个注解,比较费手,那么我 ...

  3. springMvc(实现HandlerMethodArgumentResolver)自定义参数解析器

    由于之前用@RequestParam无法接收request payload 正文格式为json格式的字符串,只能使用@RequestBody整个接收,觉得麻烦,但是spring自带的参数解析器不具有这 ...

  4. SpringMVC视图解析器

    SpringMVC视图解析器 前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视 图解析器.当我们对SpringMVC控制的资源发起 ...

  5. SpringMVC视图解析器(转)

    前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视图解析器.当我们对SpringMVC控制的资源发起请求时,这些请求都会被Sprin ...

  6. jsp springmvc 视图解析器_Java面试题整理——SpringMVC

    SpringMVC 1.什么是SpringMVC Spring MVC是一个MVC的开源框架,Spring MVC = Struts2+spring,Spring MVC就相当于是Struts2加上S ...

  7. Spring自定义参数解析器

      虽然Spring提供了比较完善的参数解析器,但是对于一些特殊的数据类型我们还是需要进行特殊处理,这样会提高代码的复杂度,增加冗余的代码,降低代码可读性和可维护性.所以自定义参数解析器是一个很好的解 ...

  8. (十六)ATP应用测试平台——java应用中的过滤器Filter、拦截器Interceptor、参数解析器Resolver、Aop切面,你会了吗?

    前言 过滤器Filter.拦截器Interceptor.参数解析器Resolver.Aop切面是我们应用开发中经常使用到的技术,到底该如何使用这些web附属功能, 本小节我们就分别介绍一下其各自的用法 ...

  9. 拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver用于统一获取当前登录用户信息

    文章目录 前言 一.拦截器+方法参数解析器 是什么? 二.具体实现步骤 1.自定义权限拦截器LoginInterceptor拦截所有request请求,并将token解析为currentUser,最终 ...

最新文章

  1. js-权威指南学习笔记7
  2. oracle查询排序速度慢,Oracle-请问Oracle SQL排序查询慢如何解决
  3. 2. 移动安全渗透测试-(Android安全基础)
  4. HTML学习笔记:iframe框架演示
  5. python装饰器详解-学习笔记-Python装饰器详解
  6. Vuex速学篇:(2)利用state保存新闻数据
  7. oracle数据库使用小结
  8. python dataframe去除重复项_python - Pandas DataFrame处理查找DataFrame中的重复项 - 堆栈内存溢出...
  9. oracle负数金额大写,Oracle 小写金额转换为大写
  10. python解压7z文件_如何读取用7z压缩的文本文件?
  11. 通过JAVA代码,将文字生成图片
  12. matplotlib+basemap画出标记地图
  13. spark封神之路(7)-RDD算子详解第一部分
  14. 【可见光室内定位】(一)概览
  15. dpdk中文-DPDK学习路线图
  16. VoLTE下视频彩铃与普通彩铃冲突的现象
  17. Educational Codeforces Round 115 (Rated for Div. 2) A. Computer Game
  18. Linux 链路聚合之bond和team
  19. Unity3D 游戏开发之内存优化
  20. afe 高通_高通ASOC中的machine驱动

热门文章

  1. 第二章 Vue快速入门-- 28 自定义按键修饰符
  2. 对搜狗输入法的个人评价
  3. MySQL表最大能达到多少?
  4. [Effective JavaScript 笔记]第29条:避免使用非标准的栈检查属性
  5. ElasticSearch2.3.1环境搭建哪些不为人知的坑
  6. Android与Javascript交互示例(二)
  7. 吴恩达 coursera ML 第五课总结+作业答案
  8. 【Leetcode】背包问题模板
  9. 【web实战2】基于源码搭建小说自动采集网站
  10. Go进阶(8): map嵌套的两轮初始化