跳到主要内容

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;

}
java
  • supportsParameter:用于判断是否能处理给定的方法参数,参数为:MethodParameterMethodParameter 中包括了参数名称、参数类型、参数上的注解等信息。
  • resolveArgument:用于进行解析处理。

与之对应的还有一个 HandlerMethodReturnValueHandler,用于处理方法返回的参数。

常见的一些子类如下:

我们常用的@RequestParam@RequestBody@PathVariable@ModelAttribute等都是通过HandlerMethodReturnValueHandler来实现的。

HandlerMethodArgumentResolverComposite

Spring在处理HandlerMethodReturnValueHandler时,用到了组合模式,定义了一个HandlerMethodArgumentResolverComposite,其内部汇集了所有的HandlerMethodReturnValueHandler,而Spring通过操作HandlerMethodArgumentResolverCompositeresolveArgument方法,来实现对参数的解析。在这个方法内会遍历所有HandlerMethodArgumentResolver,找到一个可以处理方法参数的HandlerMethodArgumentResolver,然后在调用它的resolveArgument来解析参数。

这里的妙处在于,好像只操作了这一个类的接口,但是却是等同于操作了HandlerMethodReturnValueHandler所有子类的接口。而Spring只需要关注HandlerMethodArgumentResolverComposite即可,当添加自定义实现时,也会将自定义的实现添加到HandlerMethodArgumentResolverComposite中,而Spring不需要关心HandlerMethodReturnValueHandler有哪些实现,统统交给HandlerMethodArgumentResolverComposite来处理。这也是组合模式的妙用。

如何进行断点调试?

如果想要调试Spring对请求参数的解析过程,只需要将断点打在HandlerMethodArgumentResolverCompositeresolveArgument方法中即可,或者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;
}
java

后续会解释为什么定义了两个 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

其中ServletModelAttributeMethodProcessorModelAttributeMethodProcessor的子类。

SpringMVC控制层参数加@RequestParam和不加的区别

在使用SpringMVC时,控制层的参数会经常省略掉@RequestParamModelAttribute参数注解。那省略掉注解与加上注解有区别吗?有什么区别?

参数加@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";
}
}
java

这时候如果省略掉@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来处理。

这是为什么?在ModelAttributeMethodProcessorRequestParamMethodArgumentResolver分析的章节中有详细说明。