SpringMVC系列-4 参数解析器

背景:

本文作为SpringMVC系列的第四篇,介绍参数解析器。本文讨论的参数解析表示从HTTP消息中解析出JAVA对象或流对象并传参给Controller接口的过程。
本文内容包括介绍参数解析器工作原理、常见的参数解析器、自定义参数解析器等三部分。其中,原理部分会结合源码进行说明。

1.工作原理

说明:本文重点在于说明参数解析器的工作原理和使用方式,为避免文章过于冗长,会刻意省略对异步请求和文件上传部分的分支逻辑,读者可在理解主线逻辑后自定阅读该部分源码。
源码介绍时,会忽略所有的日志打印以及与主线逻辑无关的try-catch-finnally块

1.1 解析过程

SpringMVC系列-2 HTTP请求调用链 中介绍过:收到http请求后,进入DispatcherServlet的dispatcherServlet方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {//...// 1.preHandleif (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 2.call handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 3.postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);//...
}

在执行HandlerInterceptor的preHandle和postHandle之间,会通过ha.handle(processedRequest, response, mappedHandler.getHandler())反射调用Controller接口,跟踪该调用链进入:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {//	⚠️1.调用controller接口Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);//	⚠️2.处理返回值setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}
}

invokeAndHandle方法从逻辑上可以分为两个部分:(1)调用controller接口并获取返回值;(2)处理返回值。
本文关注第一部分:Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
其中:webRequest包装了HTTP请求的request和response对象,mavContainer是MVC对象,providedArgs传入的是null.
跟进invokeForRequest方法:

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);return doInvoke(args);
}

包括两个步骤:(1)调用getMethodArgumentValues获取参数;(2)将步骤(1)获取的参数传递给doInvoke,通过反射调用Controller接口并返回结果。
跟进getMethodArgumentValues方法:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,	Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return new Object[0];}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);} catch (Exception ex) {throw ex;}}return args;
}

由于入参providedArgs为null, 因此上述逻辑可以简化为:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,	Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return new Object[0];}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);} catch (Exception ex) {throw ex;}}return args;
}

上述解析参数的逻辑可以分为两步:(1)获取目标接口的参数数组,并判断是否为空(参数为空即不需要处理参数);(2)遍历参数数组,根据参数解析器对每个参数对象依此进行处理,如果没有匹配的参数处理器,则抛出IllegalStateException异常。

这里的参数解析器this.resolvers类型是HandlerMethodArgumentResolverComposite,是一个组合模型,内部维持了一个HandlerMethodArgumentResolver数组:

private final List<HandlerMethodArgumentResolver> argumentResolvers

参数解析最终都会派发给argumentResolvers的各个元素,派发原则是选择第一个满足匹配规则的参数解析器

在后续SpringMVC源码介绍过程中,会发现框架大量使用了组合设计模式;大部分采取匹配+处理的组合手段完成。

因此HandlerMethodArgumentResolver接口需要有两个接口:

public interface HandlerMethodArgumentResolver {boolean supportsParameter(MethodParameter parameter);Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

supportsParameter方法用于判断该HandlerMethodArgumentResolver是否与参数匹配,resolveArgument方法用于解析参数并返回解析结果。

1.2 初始化过程

ServletInvocableHandlerMethods的resolvers属性
解析过程的核心在于参数解析器,即ServletInvocableHandlerMethod对象中的HandlerMethodArgumentResolverComposite resolvers属性,而每次HTTP调用都会生成一个ServletInvocableHandlerMethod对象,因此关注该对象的resolvers属性如何被初始化即可:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {// ...ServletInvocableHandlerMethod invocableMethod = new ServletInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}// ...invocableMethod.invokeAndHandle(webRequest, mavContainer);// ...}
}

如上所示:ServletInvocableHandlerMethod对象的resolvers属性来自RequestMappingHandlerAdapter对象的this.argumentResolvers属性。

RequestMappingHandlerAdapter的argumentResolvers属性
继续跟踪RequestMappingHandlerAdapter的this.argumentResolvers属性初始化过程,需要注意的是RequestMappingHandlerAdapter是全局Bean对象,因此可以从头梳理一下该对象关于argumentResolvers属性的初始化过程。
【1】实例化阶段

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();adapter.setCustomArgumentResolvers(getArgumentResolvers());// ...return adapter;
}

createRequestMappingHandlerAdapter()返回RequestMappingHandlerAdapter实例后,将getArgumentResolvers()获取的自定义参数解析器设置到this.customArgumentResolvers属性中。
看一下getArgumentResolvers()逻辑:

protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {this.argumentResolvers = new ArrayList<>();addArgumentResolvers(this.argumentResolvers);return this.argumentResolvers;
}@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {this.configurers.addArgumentResolvers(argumentResolvers);
}

argumentResolvers数据来自于this.configurers,看一下这个属性的初始化过程:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();@Autowired(required = false)public void setConfigurers(List<WebMvcConfigurer> configurers) {if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers);}}
}

configurers来自于IOC容器中WebMvcConfigurer类型的对象。

因此用户可自定义WebMvcConfigurer对象并将其注入到IOC中,在自定义的WebMvcConfigurer类中通过复写addArgumentResolvers方法可实现自定义参数解析器的添加。

【2】初始化阶段
RequestMappingHandlerAdapter实现了InitializingBean接口,在Bean的初始化阶段中,会调用其afterPropertiesSet()钩子函数:


public void afterPropertiesSet() {// ...if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// ...
}

可以看出所有的参数解析器来自有getDefaultArgumentResolvers()方法:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);// Annotation-based argument resolutionresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;
}

这里包含了框架内置的参数解析器以及自定义参数解析器,需要注意一下几点:
(1)getCustomArgumentResolvers()来自于【1】实例化阶段中设置的自定义参数解析器;
(2)自定义参数解析器的顺序比较靠后,需要避免被其他参数解析器拦截,supportsParameter方法可以根据参数类型进行匹配。
(3)首位端各存在一个RequestParamMethodArgumentResolver类型的参数解析器,区别是内部useDefaultResolution属性前者是false, 后者是true.

2.常见的参数解析器

2.1 解析器分类

Debug是阅读源码的一个很有效的方式。

Debug
Spring框架默认的消息解析器超过20个, 红框圈选的是常见的参数解析器(本章节重点对着一部分进行介绍),如下所示:在这里插入图片描述
上述26个参数解析器按照匹配条件类型可以分为:
【1】注解类型
根据Controller接口中参数是否拥有指定注解确定使用的参数解析器:
@RequestParam -> RequestParamMethodArgumentResolver, RequestParamMapMethodArgumentResolver
@PathVariable -> PathVariableMethodArgumentResolver, PathVariableMapMethodArgumentResolver
@MatrixVariable -> MatrixVariableMethodArgumentResolver, MatrixVariableMapMethodArgumentResolver
@ModelAttribute -> ModelAttributeMethodProcessor
@RequestBody -> RequestResponseBodyMethodProcessor
@RequestPart -> RequestPartMethodArgumentResolver
@RequestHeader -> RequestHeaderMethodArgumentResolver, RequestHeaderMapMethodArgumentResolver
@CookieValue -> ServletCookieValueMethodArgumentResolver
@Value -> ExpressionValueMethodArgumentResolver
@SessionAttribute -> SessionAttributeMethodArgumentResolver
@RequestAttribute -> RequestAttributeMethodArgumentResolver
上述参数解析器根据是否有对应注解(以及满足特定条件)确定是否匹配参数。

【2】参数类型
根据Controller接口中参数类型确定使用的参数解析器。
ServletResponse及其子类, OutputStream及其子类, Writer及其子类 -> ServletResponseMethodArgumentResolver
HttpEntity, RequestEntity -> HttpEntityMethodProcessor
RedirectAttributes及其子类 -> RedirectAttributesMethodArgumentResolver
Model及其子类 -> ModelMethodProcessor
Errors及其子类 -> ErrorsMethodArgumentResolver
SessionStatus -> SessionStatusMethodArgumentResolver
UriComponentsBuilder, ServletUriComponentsBuilder -> UriComponentsBuilderMethodArgumentResolver

部分参数解析器的supportsParameter方法条件比较复杂,如下所示:
ServletRequestMethodArgumentResolver:
支持的类型较多,如下所示:

@Override
public boolean supportsParameter(MethodParameter parameter) {Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||MultipartRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||Principal.class.isAssignableFrom(paramType) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);
}

MapMethodProcessor:

public boolean supportsParameter(MethodParameter parameter) {return Map.class.isAssignableFrom(parameter.getParameterType()) &&parameter.getParameterAnnotations().length == 0;
}

参数不能有注解,且参数为Map类型.

ServletModelAttributeMethodProcessor:

@Override
public boolean supportsParameter(MethodParameter parameter) {return (parameter.hasParameterAnnotation(ModelAttribute.class) ||(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));}

该解析器在实例化时设置的annotationNotRequired为true,因此可以简化为:

@Override
public boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(ModelAttribute.class) ||!BeanUtils.isSimpleProperty(parameter.getParameterType());}

表示:参数有@ModelAttribute注解或者BeanUtils.isSimpleProperty(parameter.getParameterType())返回false;

public static boolean isSimpleProperty(Class<?> type) {// 如果类型是数据调用isSimpleValueType判读数组的元素return isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType());
}public static boolean isSimpleValueType(Class<?> type) {return Void.class != type && Void.TYPE != 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);
}

即:参数类型(或参数数组的元素类型)是Void或Void.TYPE或者均不是(Enum,Number,CharSequence,Locale,…)类型的才会匹配。

2.2 常用注解及消息解析器

略(待补充)

3.使用方式

案例从请求url中解析出name和age、从HTTP请求头中解析出token、并获取当前服务器时间,用于构造User对象,并传参给Controller接口。
定义参数类:

@Data
public class User {private String name;private Integer age;private String token;private LocalDateTime time;
}

定义Controller接口:

@GetMapping("/test")
public Object queryByType(User user) {return user;
}

自定义参数解析器:

public class MyArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.getParameterType().equals(User.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {User user = new User();HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();user.setName(request.getParameter("name"));user.setAge(Integer.parseInt(request.getParameter("age")));user.setToken(request.getHeader("token"));user.setTime(LocalDateTime.now());return user;}
}

将参数解析器注册到容器中:

@Configuration
public class BaseWebMvcConfigurer implements WebMvcConfigurer {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(new MyArgumentResolver());}
}

使用postman调用测试结果如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/128783.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥等考Python组别十六级002

第一部分:选择题 1、Python L16 (15分) a和b是两个集合,它们的关系如下图所示: 以下哪个表达式的值是True?( ) a > ba < ba == ba >= b正确答案:B 2、Python

低功耗国产蓝牙芯片OM6621/HS6621- 蓝牙防丢器

在繁忙的生活中&#xff0c;我们往往会因为疏忽而丢失贵重物品&#xff0c;如钱包、钥匙、手机等&#xff0c;给生活带来不小的麻烦。然而&#xff0c;现代科技正为我们提供一种聪明的解决方案——蓝牙防丢器。这款小巧智能的装置不仅保护您的财物&#xff0c;还为您的生活带来…

swift加载h5页面空白

swift加载h5页面空白 problem 背景 xcode swift 项目&#xff0c;WebView方式加载h5页面本地h5地址是&#xff1a;http://localhost:5173/ 浏览器打开正常 Swift 加载h5&#xff1a; 百度官网 加载正常本地h5页面 加载空白&#xff0c;没有报错 override func viewDidLoad…

系统架构设计:1论软件系统建模方法及其应用

目录 一 软件系统建模方法 1结构化建模 2信息工程建模 3面向对象建模 4功能分解法 5基于构件的开发方法 一 软件系统建模方法 软件建模体现了软件设计的思想&#xff0c;是连接需求和实现的桥梁&#xff0c;用于指导软件的具体实现。软件模型不是软件系统的完备表示&…

解决maven骨架加载慢问题(亲测解决)

1、下载archetype-catalog.xml 网站 &#xff1a; https://repo.maven.apache.org/maven2/ 2、放在这个文件夹下面 3、setting–>build–>Runner : -DarchetypeCataloglocal

uni-app--》基于小程序开发的电商平台项目实战(四)

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

OLED透明屏技术在智能手机、汽车和广告领域的市场前景

OLED透明屏技术作为一种新型的显示技术&#xff0c;具有高透明度、触摸和手势交互、高画质和图像显示效果等优势&#xff0c;引起了广泛的关注。 随着智能手机、汽车和广告等行业的快速发展&#xff0c;OLED透明屏技术也在这些领域得到了广泛的应用。 本文将介绍OLED透明屏技…

day58:ARMday5,GPIO流水灯实验

汇编指令&#xff1a; .text .global _start _start: 1.设置GPIOE GPIOF寄存器的时钟使能 RCC_MP_AHB4ENSETR[5:4]->1 0x50000a28 LDR R0,0x50000a28 LDR R1,[R0] ORR R1,R1,#(0x3<<4) STR R1,[R0]2.设置PE10、PF10、PE8管脚为输出模式&#xff0c;GPIOE_MODER[21…

TensorFlow入门(十四、数据读取机制(1))

TensorFlow的数据读取方式 TensorFlow的数据读取方式共有三种,分别是: ①预加载数据(Preloaded data) 预加载数据的方式,其实就是静态图(Graph)的模式。即将数据直接内嵌到Graph中,再把Graph传入Session中运行。 示例代码如下: import tensorflow.compat.v1 as tf tf.disabl…

UE4游戏客户端开发进阶学习指南

前言 两年多前写过一篇入门指南&#xff0c;教大家在短时间内快速入门UE4的使用&#xff0c;在知乎被很多人收藏了。如今鸡佬使用UE快三年了&#xff0c;是时候更新一下进阶版本的学习指南。本文对于读者的要求&#xff1a; 有一定的C基础已经入门UE&#xff0c;能够用蓝图和…

这道面试题工作中经常碰到,但 99% 的程序员都答不上来

小时候都被问过一个脑筋急转弯&#xff0c;把大象放进冰箱有几个步骤&#xff1f;我们一开始都会抓耳挠腮&#xff0c;去想着该如何把大象塞进冰箱。最终揭晓的答案却根本不关心具体的操作方法&#xff0c;只是提供了 3 个步骤组成的流程&#xff0c;「把冰箱打开&#xff0c;把…

湖南互联网医院|湖南互联网医院牌照办理流程及材料

互联网医牌照&#xff0c;一个让医疗行业焕发数字化新生的通行证。随着时代的进步和技术的发展&#xff0c;互联网已经深入各个行业&#xff0c;医疗领域也不例外。而互联网医牌照的办理流程、内容以及所需材料&#xff0c;则是诸多医疗机构所关注的核心内容。 第一种是实体医…