SpringMVC 参数解析器
一、问题
springMVC对于下面这种接口,参数是怎么解析的:
@GetMapping("/hello/{id}")
public void hello3(@PathVariable Long id) {System.out.println("id = " + id);
}
这是我们日常中最常见的参数定义方式,相信很多小伙伴对此很感兴趣。由于这块涉及到一个非常庞大的类AbstractNamedValueMethodArgumentResolver
,因此这里我单独写了一篇文章来和大家分享这个问题。在正式分享之前,我们先来整体看看参数解析器都有哪些。
二、参数解析器
HandlerMethodArgumentResolver 就是我们口口声声说的参数解析器,它的实现类还是蛮多的,因为每一种类型的参数都对应了一个参数解析器
:
为了理解方便,我们可以将这些参数解析器分为四大类:
- xxxMethodArgumentResolver:这就是一个普通的参数解析器。
- xxxMethodProcessor:
不仅可以当作参数解析器,还可以处理对应类型的返回值。
- xxxAdapter:这种不做参数解析,
仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器
。- HandlerMethodArgumentResolverComposite:这个看名字就知道是一个组合解析器,
它是一个代理,具体代理其他干活的那些参数解析器
。
大致上可以分为这四类,其中最重要的当然就是前两种
了。springMVC中有26中参数解析器
三、参数解析器概览
接下来我们来先来大概看看这些参数解析器分别都是用来干什么的。
- MapMethodProcessor
这个用来处理 Map/ModelMap 类型的参数
,解析完成后返回 model。
- PathVariableMethodArgumentResolver
这个用来处理使用了 @PathVariable 注解并且参数类型不为 Map 的参数
,参数类型为 Map 则使用 PathVariableMapMethodArgumentResolver 来处理。
- PathVariableMapMethodArgumentResolver
见上。
- ErrorsMethodArgumentResolver
这个用来处理 Error 参数,例如我们做参数校验时的 BindingResult
。
- AbstractNamedValueMethodArgumentResolver
这个用来处理 key/value 类型的参数,如请求头参数、使用了 @PathVariable 注解的参数以及 Cookie 等
。
- RequestHeaderMethodArgumentResolver
这个用来处理使用了 @RequestHeader 注解,并且参数类型不是 Map 的参数
(参数类型是 Map 的使用 RequestHeaderMapMethodArgumentResolver)。
- RequestHeaderMapMethodArgumentResolver
见上。
- RequestAttributeMethodArgumentResolver
这个用来处理使用了 @RequestAttribute 注解的参数
。
- RequestParamMethodArgumentResolver
这个功能就比较广了。使用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理
。需要注意的是,如果 @RequestParam 注解的参数类型是 Map,则该注解必须有 name 值,否则解析将由 RequestParamMapMethodArgumentResolver 完成。
- RequestParamMapMethodArgumentResolver
见上。
- AbstractCookieValueMethodArgumentResolver
这个是一个父类,处理使用了 @CookieValue 注解的参数
。
- ServletCookieValueMethodArgumentResolver
这个处理使用了 @CookieValue 注解的参数
。
- MatrixVariableMethodArgumentResolver
这个处理使用了 @MatrixVariable 注解并且参数类型不是 Map 的参数
,如果参数类型是 Map,则使用 MatrixVariableMapMethodArgumentResolver 来处理。
- MatrixVariableMapMethodArgumentResolver
见上。
- SessionAttributeMethodArgumentResolver
这个用来处理使用了 @SessionAttribute 注解的参数
。
- ExpressionValueMethodArgumentResolver
这个用来处理使用了 @Value 注解的参数
。
- ServletResponseMethodArgumentResolver
这个用来处理 ServletResponse、OutputStream 以及 Writer 类型的参数
。
- ModelMethodProcessor
这个用来处理 Model 类型参数
,并返回 model。
- ModelAttributeMethodProcessor
这个用来处理使用了 @ModelAttribute 注解的参数
。
- SessionStatusMethodArgumentResolver
这个用来处理 SessionStatus 类型的参数。
- PrincipalMethodArgumentResolver
这个用来处理 Principal 类型参数
。
- AbstractMessageConverterMethodArgumentResolver
这是一个父类,当使用 HttpMessageConverter 解析 requestbody 类型参数时,相关的处理类都会继承自它。
- RequestPartMethodArgumentResolver
这个用来处理使用了 @RequestPart 注解、MultipartFile 以及 Part 类型的参数。
- AbstractMessageConverterMethodProcessor
这是一个工具类,不承担参数解析任务。
- RequestResponseBodyMethodProcessor
这个用来处理添加了 @RequestBody 注解的参数
。
- HttpEntityMethodProcessor
这个用来处理 HttpEntity 和 RequestEntity 类型的参
数。
ContinuationHandlerMethodArgumentResolver
AbstractWebArgumentResolverAdapter
这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。
- ServletWebArgumentResolverAdapter
这个给父类提供 request。
- UriComponentsBuilderMethodArgumentResolver
这个用来处理 UriComponentsBuilder 类型的参数
。
- ServletRequestMethodArgumentResolver
这个用来处理 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数
。
- HandlerMethodArgumentResolverComposite
这个看名字就知道是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。
- 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;
}
- 首先
根据当前请求获取一个 NamedValueInfo 对象,这个对象中保存了参数的三个属性:参数名、参数是否必须以及参数默认值。
具体的获取过程就是先去缓存中拿,缓存中如果有,就直接返回,缓存中如果没有,则调用 createNamedValueInfo 方法去创建,将创建结果缓存起来并返回。createNamedValueInfo 方法是一个模版方法,具体的实现在子类中。 - 接下来处理 Optional 类型参数。
- resolveEmbeddedValuesAndExpressions 方法是
为了处理注解中使用了 SpEL 表达式的情况
,例如如下接口:
@GetMapping("/hello2")
public void hello2(@RequestParam(value = "${aa.bb}") String name) {System.out.println("name = " + name);
}
参数名使用了表达式,那么 resolveEmbeddedValuesAndExpressions 方法的目的就是解析出表达式的值,如果没用到表达式,那么该方法会将原参数原封不动返回。
- 接下来调用 resolveName 方法解析出参数的具体值,这个方法也是一个模版方法,具体的实现在子类中。
- 如果获取到的参数值为 null,先去看注解中有没有默认值,然后再去看参数值是否是必须的,如果是,则抛异常出来,否则就设置为 null 即可。
- 如果解析出来的参数值为空字符串 “”,则也去 resolveEmbeddedValuesAndExpressions 方法中走一遭。
- 最后则是 WebDataBinder 的处理,解决一些全局参数的问题,WebDataBinder 松哥在之前的文章中也有介绍过,传送门: @ControllerAdvice 的三种使用场景。
大致的流程就是这样。
在这个流程中,我们看到主要有如下两个方法是在子类中实现的:
- createNamedValueInfo
- resolveName
在加上 supportsParameter 方法,子类中一共有三个方法需要我们重点分析。那么接下来我们就以 RequestParamMethodArgumentResolver
为例,来看下这三个方法。
五、RequestParamMethodArgumentResolver
- 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 方法中可以非常方便的看出支持的参数类型:
- 首先参数如果有 @RequestParam 注解的话,则分两种情况:
参数类型如果是 Map,则 @RequestParam 注解必须配置name 属性,否则不支持;如果参数类型不是 Map,则直接返回 true,表示总是支持(想想自己平时使用的时候是不是这样)。
- 参数如果含有@RequestPart 注解,则不支持。
- 检查
是不是文件上传请求,如果是,返回 true 表示支持
。- 如果前面都没能返回,则使用默认的解决方案,
判断是不是简单类型,主要就是 Void、枚举、字符串、数字、日期等等。
这块代码其实很简单,支持谁不支持谁,一目了然。
- 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 对象返回。
- 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;
}
这个方法思路也比较清晰:
- 前面两个 if 主要是为了处理文件上传请求。
如果不是文件上传请求,则调用 request.getParameterValues方法取出参数返回即可。
文章转自
SpringMVC 参数解析器相关推荐
- springmvc自定义参数解析器
由于开发中一般使用参数提交方式是json格式,对于单个参数的传递使用无法接收只能自定义参数解析器处理 springmvc的自定义参数解析器实现HandlerMethodArgumentResolver ...
- springmvc自定义参数解析器/类型转换器
概述 有些时候我们需要对GET请求的入参做自定义的处理,比较常见的就是字符串反序列化时间类型了,常用的像@DateTimeFormat注解,但是这需要在每个入参的属性上都加上这个注解,比较费手,那么我 ...
- springMvc(实现HandlerMethodArgumentResolver)自定义参数解析器
由于之前用@RequestParam无法接收request payload 正文格式为json格式的字符串,只能使用@RequestBody整个接收,觉得麻烦,但是spring自带的参数解析器不具有这 ...
- SpringMVC视图解析器
SpringMVC视图解析器 前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视 图解析器.当我们对SpringMVC控制的资源发起 ...
- SpringMVC视图解析器(转)
前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视图解析器.当我们对SpringMVC控制的资源发起请求时,这些请求都会被Sprin ...
- jsp springmvc 视图解析器_Java面试题整理——SpringMVC
SpringMVC 1.什么是SpringMVC Spring MVC是一个MVC的开源框架,Spring MVC = Struts2+spring,Spring MVC就相当于是Struts2加上S ...
- Spring自定义参数解析器
虽然Spring提供了比较完善的参数解析器,但是对于一些特殊的数据类型我们还是需要进行特殊处理,这样会提高代码的复杂度,增加冗余的代码,降低代码可读性和可维护性.所以自定义参数解析器是一个很好的解 ...
- (十六)ATP应用测试平台——java应用中的过滤器Filter、拦截器Interceptor、参数解析器Resolver、Aop切面,你会了吗?
前言 过滤器Filter.拦截器Interceptor.参数解析器Resolver.Aop切面是我们应用开发中经常使用到的技术,到底该如何使用这些web附属功能, 本小节我们就分别介绍一下其各自的用法 ...
- 拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver用于统一获取当前登录用户信息
文章目录 前言 一.拦截器+方法参数解析器 是什么? 二.具体实现步骤 1.自定义权限拦截器LoginInterceptor拦截所有request请求,并将token解析为currentUser,最终 ...
最新文章
- js-权威指南学习笔记7
- oracle查询排序速度慢,Oracle-请问Oracle SQL排序查询慢如何解决
- 2. 移动安全渗透测试-(Android安全基础)
- HTML学习笔记:iframe框架演示
- python装饰器详解-学习笔记-Python装饰器详解
- Vuex速学篇:(2)利用state保存新闻数据
- oracle数据库使用小结
- python dataframe去除重复项_python - Pandas DataFrame处理查找DataFrame中的重复项 - 堆栈内存溢出...
- oracle负数金额大写,Oracle 小写金额转换为大写
- python解压7z文件_如何读取用7z压缩的文本文件?
- 通过JAVA代码,将文字生成图片
- matplotlib+basemap画出标记地图
- spark封神之路(7)-RDD算子详解第一部分
- 【可见光室内定位】(一)概览
- dpdk中文-DPDK学习路线图
- VoLTE下视频彩铃与普通彩铃冲突的现象
- Educational Codeforces Round 115 (Rated for Div. 2) A. Computer Game
- Linux 链路聚合之bond和team
- Unity3D 游戏开发之内存优化
- afe 高通_高通ASOC中的machine驱动