文章目录 一、前言二、正文1、参数处理器HandlerMethodArgumentResolver的加载1)《门面》HandlerMethodArgumentResolverComposite2)参数解析器HandlerMethod
Spring MVC源码分析相关文章已出:
更多spring系列源码分析文章见SpringBoot专栏:
当前文章Spring版本:5.2.12.RELEASE,不同版本类名可能不同,但核心思想都是一样的。
从springMVC的执行流程(参考博文:Spring MVC请求执行流程)来看,RequestMappingHandlerAdapter这个HandlerAdapter负责执行Controller中的相应方法,并对请求参数进行处理。
RequestMappingHandlerAdapter实现了InitializingBean接口,因此在SpringBoot启动过程中实例化RequestMappingHandlerAdapter过程中(执行init-method()方法之前),会调用RequestMappingHandlerAdapter#afterPropertiesSet()方法:
@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}} 
RequestMappingHandlerAdapter中的参数解析器变量argumentResolvers的类型是 HandlerMethodArgumentResolverComposite;HandlerMethodArgumentResolverComposite又组合了所有的HandlerMethodArgumentResolver;

因此可以将HandlerMethodArgumentResolverComposite看做是所有参数解析器的“门面”,但凡需要用到参数解析器都都将请求先打到HandlerMethodArgumentResolverComposite。
EventPublishingRunListener 异曲同工。

getDefaultArgumentResolvers()方法获取所有的参数解析器HandlerMethodArgumentResolver时就是单纯的new,然后放到List中。

获取到所有的HandlerMethodArgumentResolver之后,直接new一个HandlerMethodArgumentResolverComposite,然后将所有的HandlerMethodArgumentResolver都添加到HandlerMethodArgumentResolverComposite中。
从RequestMappingHandlerAdapter#getDefaultArgumentResolvers()方法获取所有的参数解析器HandlerMethodArgumentResolver中注释来看,Spring mvc中将参数解析器分为四大类:
customArgumentResolvers 属性中。http://localhost:8080/trace/test?name=saint

参数解析器的应用顺序实际就是越晚添加到List集合中,就越最后被遍历到。
日常开发过程中一般不需要自定义参数解析器,就可以覆盖90%以上的开发需求。下一篇文章输出自定义参数解析器。
我们知道请求是交给HandlerAdapter执行的,对于普通的HTTP请求也就是交给RequestMappingHandlerAdapter执行,RequestMappingHandlerAdapter要执行某个方法时,会将RequestMappingHandlerAdapter中的参数解析器“门面”绑定给方法。

继续往下跟,到真正解析前的代码路径如下:

InvocableHandlerMethod#getMethodArgumentValues()方法会应用相应的参数解析器从请求中解析出方法的参数值。
根据方法参数标注的注解、参数类型会选择不同的参数解析器HandlerMethodArgumentResolver。有种策略模式的意思。
选择参数解析器HandlerMethodArgumentResolver时,有一层小优化,针对每个方法的参数对应的参数解析器都会做一个缓存。如果从缓存中找不到方法参数对应的参数解析器,则遍历所有的参数解析器(默认26个),调用参数解析器的supportsParameter()方法判断参数解析器是否支持当前参数的解析,如果支持则直接返回。

下面以不同的入参形式暂开具体的讨论。

@RequestParam常用于GET请求,注解里的value或name互为别名、对应请求的参数名,required()属性表示参数是否必须存在。
分析以如下请求为例:

这里特意将方法的参数名和@RequestParam中的value设置为不一样的值。
RequestParamMethodArgumentResolver负责解析被@RequestParam标注的参数。

该实现类仅支持处理被@RequestParam注解标注的参数(参数如果是Map类型,@RequestParam注解里的name/value必须有值,否则不知道应该拿哪个KV键值对)。
如果参数没有被@RequestParam注解标注,走进的逻辑是兜底的参数解析逻辑。

解析参数会走入到HandlerMethodArgumentResolver#resolveArgument()方法;
Spring MVC设计了一个抽象父类AbstractNamedValueMethodArgumentResolver,用于给参数解析器提供一种通用的模板实现,子类通过重写模板中的某个方法实现自己的逻辑。
RequestParamMethodArgumentResolver亦是AbstractNamedValueMethodArgumentResolver的子类,所以执行RequestParamMethodArgumentResolver#resolveArgument()实际会进入到:

@Override@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWEBRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 1. 根据方法入参获取参数的配置信息(针对@RequestParam为:拿到参数的@RequestParam注解属性)NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional(); // 2. 根据上面参数配置的name 获取真正的参数nameObject resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");} // 3. 根据参数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);} // 4. 如果Controller上有配置@InitBinder,且作用于该request,则通过WebDataBinder里的TypeConverter看是否需要对此类型参数进行转化;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());}} // 5. 调用方法handleResolvedValue()handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); // 6. 返回参数值return arg;} AbstractNamedValueMethodArgumentResolver#resolveArgument()方法主要做五件事:
createNamedValueInfo()方法决定。resolveName()方法决定。RequestParamMethodArgumentResolver针对第一步(getNamedValueInfo()) 和 第三步 (resolveName())做了重写;
进入到createNamedValueInfo()的链路如下:

RequestParamMethodArgumentResolver#createNamedValueInfo()方法只是简单的提取@RequestParam注解的属性;


方法分为两部分:
request.getFiles()方法获取参数名对应的MultipartFile作为参数值。name=1&name=2,所以返回的是数组。
@PathVariable注解用于将URL中的占位符转换为入参,其中name()和value()属性互为别名;
分析以如下请求为例:

这里特意将方法的参数名和@PathVariable中的value设置为不一样的值。
PathVariableMethodArgumentResolver参数解析器用于对@PathVariable注解标注的参数进行解析。
PathVariableMethodArgumentResolver间接实现了HandlerMethodArgumentResolver接口,直接继承了AbstractNamedValueMethodArgumentResolver抽象类。
判断PathVariableMethodArgumentResolver是否可以解析某个参数的逻辑如下:

仅有被@PathVariable注解标注的参数(参数如果是Map类型,@PathVariable注解里的name/value必须有值,否则不知道应该拿哪个KV键值对)。
这个逻辑和@RequestParam的一致。

仅仅是提取@PathVariable注解的属性;

根据参数名获取参数值时,这里只是简单的从请求的属性(org.springframework.web.servlet.HandlerMapping.uriTemplateVariables)中获取。
org.springframework.web.servlet.HandlerMapping.uriTemplateVariables这个属性的值是从哪来的?

从这里可以看出方法的入参名是什么无所谓,只需要注解中的name()和请求中的参数能对上就OK。

@RequestBody注解常用于POST请求,将http请求体转换成Java对象类型的参数;
Content-Type=application/json,后端用一个JAVA对象接收。分析以如下请求为例:

RequestResponseBodyMethodProcessor负责解析被@RequestBody标注的参数。
RequestResponseBodyMethodProcessor间接实现了HandlerMethodArgumentResolver接口;下面看其supportsParameter()方法如何判断当前参数解析器支持解析的参数数据。
RequestResponseBodyMethodProcessor#supportsParameter()方法的逻辑表示:当前参数解析器只处理有@RequestBody注解的参数。




RequestResponseBodyMethodProcessor继承了抽象类AbstractMessageConverterMethodProcessor,并且没有重写readWithMessageConverters()方法,因此通过MessageConvert读取数据到Java类型对象中的逻辑在AbstractMessageConverterMethodProcessor类中。

AbstractMessageConverterMethodProcessor#readWithMessageConverters()方法主要做三件事:
针对ContentType = “application/json”的请求参数,AbstractJackson2HttpMessageConverter可以对参数进行处理;

读取方法readJavaType() 内部就是调用jackson包的相关类将InputStream转换为Java类型。

最后解析出的参数值如下:

在实例化RequestMappingHandlerAdapter时,会将所有的MessageConverter加载到RequestResponseBodyMethodProcessor中:
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); 
默认情况下有十个HttpMessageConverter:

分析以如下请求为例:

针对Request类型的参数,处理起来很简单,都是直接从请求中获取相应对象信息。
就HttpServletRequest而言,ServletRequestMethodArgumentResolver参数解析器负责处理;
ServletRequestMethodArgumentResolver支持处理的参数如下:

都是Request相关的类型 或 Request里的属性类型,比如:请求body(InputStream)、请求方式(HttpMethod)、国际化信息(Locale / TimeZone)

解析参数的逻辑就是对类型判断,根据不同的参数类型从webRequest取对象。

Map入参由MapMethodProcessor进行解析
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { @Override public boolean supportsParameter(MethodParameter parameter) { return (Map.class.isAssignableFrom(parameter.getParameterType()) && parameter.getParameterAnnotations().length == 0); } @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure"); return mavContainer.getModel(); } ....} Model入参由ModelMethodProcessor进行解析
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { @Override public boolean supportsParameter(MethodParameter parameter) { return Model.class.isAssignableFrom(parameter.getParameterType()); } @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure"); return mavContainer.getModel(); } ....} 无论是Model类型还是Map类型的参数,其相应参数解析器中的resolveArgument()方法并没有具体的参数处理逻辑。
分析以如下请求为例:

RequestParamMethodArgumentResolver参数解析器不仅用于解析@RequestParam、还用于特定的兜底参数解析场景;

PS:解析@RequestParam 和 兜底参数解析用到的RequestParamMethodArgumentResolver是两个不同的实例对象

因为此处RequestParamMethodArgumentResolver的父类AbstractNamedValueMethodArgumentResolver调用getNamedValueInfo方法获取参数信息时为空;

进入updateNamedValueInfo()方法时会使用反射获取到参数名。
参数解析器的加载时机:
InitializingBean接口,实例化其之后会调用afterPropertiesSet()方法,其中会对参数解析器进行赋值。 argumentResolvers类型为HandlerMethodArgumentResolverComposite;HandlerMethodArgumentResolverComposite是一个门面,其中组合了所有的参数解析器HandlerMethodArgumentResolver参数解析器分为四大类:注解类型、Request请求类型、自定义、兜底实现;
具体选择哪种参数解析器,由参数解析器HandlerMethodArgumentResolver#supportsParameter()方法决定;
此外,Spring MVC设计了一个抽象父类AbstractNamedValueMethodArgumentResolver,用于给参数解析器提供一种通用的模板实现,子类通过重写模板中的某个方法实现自己的逻辑。
来源地址:https://blog.csdn.net/Saintmm/article/details/129412117
--结束END--
本文标题: 【源码篇】Spring MVC多种请求入参处理方式都在这了(@RequestParam、@PathVariable、@RequestBody、Map、JavaModel、Request、基础类型)
本文链接: https://www.lsjlt.com/news/374511.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0