HandlerMethodArgumentResolver
在开始讲解Spring参数解析原理之前,我们需要先来了解一个接口:HandlerMethodArgumentResolver
。这个接口我们很少接触。它的作用就是扩展控制层方法的解析功能。其源码如下:
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
supportsParameter
:用于判断是否能处理给定的方法参数,参数为:MethodParameter
。MethodParameter
中包括了参数名称、参数类型、参数上的注解等信息。resolveArgument
:用于进行解析处理。
与之对应的还有一个
HandlerMethodReturnValueHandler
,用于处理方法返回的参数。
常见的一些子类如下:
我们常用的@RequestParam
、@RequestBody
、@PathVariable
、@ModelAttribute
等都是通过HandlerMethodReturnValueHandler
来实现的。
HandlerMethodArgumentResolverComposite
Spring在处理HandlerMethodReturnValueHandler
时,用到了组合模式,定义了一个HandlerMethodArgumentResolverComposite
,其内部汇集了所有的HandlerMethodReturnValueHandler
,而Spring通过操作HandlerMethodArgumentResolverComposite
的resolveArgument
方法,来实现对参数的解析。在这个方法内会遍历所有HandlerMethodArgumentResolver
,找到一个可以处理方法参数的HandlerMethodArgumentResolver
,然后在调用它的resolveArgument
来解析参数。
这里的妙处在于,好像只操作了这一个类的接口,但是却是等同于操作了HandlerMethodReturnValueHandler
所有子类的接口。而Spring只需要关注HandlerMethodArgumentResolverComposite
即可,当添加自定义实现时,也会将自定义的实现添加到HandlerMethodArgumentResolverComposite
中,而Spring不需要关心HandlerMethodReturnValueHandler
有哪些实现,统统交给HandlerMethodArgumentResolverComposite
来处理。这也是组合模式的妙用。
如何进行断点调试?
如果想要调试Spring对请求参数的解析过程,只需要将断点打在HandlerMethodArgumentResolverComposite
的resolveArgument
方法中即可,或者supportsParameter
方法即可。这里便是参数解析处理的入口。
HandlerMethodArgumentResolver的加载顺序
从 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
中可以找到HandlerMethodArgumentResolver
的加载顺序。具体如下。
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.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 resolution
resolvers.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());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// 自定义的解析器在这个位置。
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// 以上解析器都无法处理的时候,下面这三个照单全收。
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
后续会解释为什么定义了两个 RequestParamMethodArgumentResolver
和两个 ServletModelAttributeMethodProcessor
。
从两个问题引出各个参数解析器的工作原理
我们从两个问题开始本章。
- 第一个 问题:SpringMVC控制层参数加@RequestParam和不加的区别?
- 第二个问题:SpringMVC Get请求中如何正确接受集合类型参数?
先看下本章中的核心类
org.springframework.web.method.support.HandlerMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.ModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
其中ServletModelAttributeMethodProcessor
是ModelAttributeMethodProcessor
的子类。
SpringMVC控制层参数加@RequestParam和不加的区别
在使用SpringMVC时,控制层的参数会经常省略掉@RequestParam
和ModelAttribute
参数注解。那省略掉注解与加上注解有区别吗?有什么区别?
参数加@RequestParam
和不加的区别是什么呢?通过断点调试发现,如果集合类的参数不加@RequestParam
那么最终会匹配到ModelAttributeMethodProcessor
来解析参数,而加上@RequestParam
则会匹配到RequestParamMethodArgumentResolver
来解析我们的参数。
或者说不同的参数注解与不同的参数类型会由不同的 HandlerMethodArgumentResolver
来处理。
这个我们在后面的文章中分析某些具 体的HandlerMethodArgumentResolver
时会详细讲解。
Get请求接收集合类型参数
控制层代码如下
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/set")
public String setTest(Set<String> paramSet) {
System.out.println(paramSet);
return "OK";
}
}
这时候如果省略掉@RequestParam
,当发起请求时,会报错。如当我们发起如下请求时
GET http://localhost:8080/test/set?paramSet=1,2,3
系统抛出异常:No primary or single unique constructor found for interface java.util.Set
。详细的异常栈如下
java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.Set
at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[spring-beans-6.1.4.jar:6.1.4]
at org.springframework.validation.DataBinder.createObject(DataBinder.java:924) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.validation.DataBinder.construct(DataBinder.java:903) ~[spring-context-6.1.4.jar:6.1.4]
at org.springframework.web.bind.ServletRequestDataBinder.construct(ServletRequestDataBinder.java:116) ~[spring-web-6.1.4.jar:6.1.4]
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.constructAttribute(ServletModelAttributeMethodProcessor.java:156) ~[spring-webmvc-6.1.4.jar:6.1.4]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:148) ~[spring-web-6.1.4.jar:6.1.4]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-6.1.4.jar:6.1.4]
而当我们给参数加上@RequestParam
的时候,请求就可以正常处理了。输出结果如下:
[1, 2, 3]
那显然加上@RequestParam
和不加应用的是两种不同的处理策略。
如果不加@RequestParam
则应用ModelAttributeMethodProcessor
来进行参数解析,如果加上,则由RequestParamMethodArgumentResolver
来处理。
这是为什么?在ModelAttributeMethodProcessor
和RequestParamMethodArgumentResolver
分析的章节中有详细说明。