1.前言
在上一篇文章中,我们留下了好几个点没有分析,其中有一个就是HandlerMethodArgumentResolver类,这个类呢,就是用于处理方法的参数解析
。用于比如@RequestBody,@RequestParam以及我们自定义等的参数处理。
2.例子
我们这里有一个例子,根据请求头是否有jiang来鉴权,没有就抛出异常。这里例子比较简单,如果真正业务场景的话,可能就会做更多事情。
1.注解
就是一个简单的@CurrentUser
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}
2.解析器定义
参数解析器有两个方法:
1.supportsParameter
:用于判断是否该解析器支持解析
2.resolveArgument
:当解析器支持的话,就会调用该方法解析参数对象
这里的解析就是判断请求头有没有带有name是jiang的参数,有就返回
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String name = request.getHeader("name");
if (!"jiang".equals(name)) {
throw new RuntimeException("鉴权错误");
}
return name;
}
}
3.配置解析器
下面的代码也比较简单,主要是注入了一个WebMvcConfigurer
的Bean,这个Bean重写了addArgumentResolvers
,将我们的CurrentUserArgumentResolver
参数解析器加入了。
@Slf4j
@Configuration
public class BootWebConfiguration {
@Bean
WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserArgumentResolver());
}
};
}
}
3.参数解析器加载流程
上面我们看到了只要注入一个WebMvcConfigurer
,然后里面实现方法即可让这个参数解析器生效。那这个解析器又是如何被使用生效的呢,下面开始分析
1.SpringBoot对SpringMVC的加载
众所周知springboot的对各种组件,类似redis,kafka等都是用starter
的模式,通过spring.factories
文件来加载。
SpringMVC中也不例外。
在org.springframework.boot:spring-boot-autoconfigure
包中的spring.factories
。
# .......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
# ......
上面有很多AutoConfiguration
,其中包括DispatcherServlet
的,以及ServletWebServerFactory
用于加载tomcat,还是jetty容器等等。
其中WebMvcAutoConfiguration
就是我们启动的主角了。
2.WebMvcAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
// *****
}
先看定义,这有其中有一个要求是@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,也就是如果我们自定义了WebMvcConfigurationSupport
类型的Bean,那这个WebMvcAutoConfiguration
就不会生效了。
所以这里也涉及到了一个常见的错误,很多人要加请求拦截器,或者啥的都继承WebMvcConfigurationSupport
,然后重写里面的方法,这样写是不太规范的,最好是像我们上面的一样,注入WebMvcConfigurer来实现。
再继续看里面的代码,会发现有个内部的Bean
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
// 注入RequestMappingHandlerAdapter
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
conversionService, validator);
adapter.setIgnoreDefaultModelOnRedirect(
this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
return adapter;
}
// 注入RequestMappingHandlerMapping
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
resourceUrlProvider);
}
这里会注入RequestMappingHandlerAdapter
,以及RequestMappingHandlerMapping
。根据上两篇文章分析,这两个Bean,就是用于我们@RequestMapping
的这种形式的处理hander,以及handlerAdapter。
我们既然是分析对参数的解析,那自然是要看RequestMappingHandlerAdapter
这个在请求流程中至关重要Bean的注入了。
我们看到它调用了super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator);
我们先看一下EnableWebMvcConfiguration
它的类继承关系
这里可以看到DelegatingWebMvcConfiguration
这个类其实是个中间层的代理类。它有个函数setConfigurers
,这里会将所有的WebMvcConfigurer
都注入进来,然后加入到WebMvcConfigurerComposite
。
然后其他的方法,就是挨个调用WebMvcConfigurerComposite
的WebMvcConfigurer
来处理。
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// WebMvcConfigurer的组合类
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
// ******
接着往上看它的父类WebMvcConfigurationSupport
,它的东西就是核心了。
3.WebMvcConfigurationSupport
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
// 创建RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
// ...
return mapping;
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(contentNegotiationManager);
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
return adapter;
}
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
}
上面我们可以看到,它实现了创建RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
,同时还留了addArgumentResolvers,addReturnValueHandlers这种接口给子类去实现。
那我们先研究RequestMappingHandlerAdapter
,因为这里可以看到有一句,adapter.setCustomArgumentResolvers(getArgumentResolvers());
用于设置参数解析器。
同时这里也是有adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
用于设置返回值处理器。
这里我们先看参数解析器的逻辑,其实和设置返回值处理器的逻辑是一致的。
发现会调用getArgumentResolvers
protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
if (this.argumentResolvers == null) {
this.argumentResolvers = new ArrayList<>();
addArgumentResolvers(this.argumentResolvers);
}
return this.argumentResolvers;
}
第一次的话这个argumentResolvers肯定是空的,所以会调用addArgumentResolvers
,注意,这里是传了一个List进去,意味着如果要增加参数解析器只需要向这里面添加就好了,而这个刚好是由DelegatingWebMvcConfiguration
实现了。
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
在看configurers的addArgumentResolvers
lass WebMvcConfigurerComposite implements WebMvcConfigurer {
// Web配置类列表
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
// 遍历WebMvcConfigurer,调用addArgumentResolvers
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addArgumentResolvers(argumentResolvers);
}
}
// 遍历WebMvcConfigurer,调用addReturnValueHandlers
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addReturnValueHandlers(returnValueHandlers);
}
}
这个XXXComposite模式是不是有点似曾相似,没错,前面HandlerMethodArgumentResolverComposite
啥的,都是和这个一个套路,将所有的WebMvcConfigurer
配置类都组合到一起,做增强处理,同时自己也继承了WebMvcConfigurer
。
而这个configurers
在DelegatingWebMvcConfiguration.setConfigurers
就将Spring中的所有WebMvcConfigurer
都注入了。
所以,当调用WebMvcConfigurationSupport.getArgumentResolvers的时候,就会调用到DelegatingWebMvcConfiguration
的addArgumentResolvers
,然后调用到我们自己的代码:
@Bean
WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(handlerInterceptor());
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserArgumentResolver());
}
};
}
最终将这个参数解析器放入到WebMvcConfigurationSupport
的argumentResolvers
中。
那么到这里,我们已经将我们自己定义的参数解析器放入到了处理器适配器RequestMappingHandlerAdapter
的customArgumentResolvers中了。
4.RequestMappingHandlerAdapter中的参数解析器
上面的第三步,我们已经将CurrentUserArgumentResolver
这个自定义的参数解析器放到了RequestMappingHandlerAdapter的customArgumentResolvers
中了。接下来,这个RequestMappingHandlerAdapter
就要初始化了。
可以看到RequestMappingHandlerAdapter
是实现InitializingBean,所以会进入afterPropertiesSet方法
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
// 参数解析器
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);
}
}
这里有getDefaultArgumentResolvers(),然后获取出来了以后,创建一个HandlerMethodArgumentResolverComposite
,赋值给argumentResolvers
可以看下这个getDefaultArgumentResolvers的代码:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// @RequestParam的解析
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
// @PathVariable的解析
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
// @RequestBody的解析
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
// .....
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;
}
这里可以看到其实默认的参数解析器是有很多的,比如处理@RequestParam的,@PathVariable的,以及@RequestBody的,我们的自定义会在getCustomArgumentResolvers()
取出,然后加入进去。返回值参数处理器,也是类似的。
所以这里也有小的知识点:就是我们自己定义的参数解析器顺序是比较靠后的,在参数解析的时候,都是优先遍历放在前面的参数解析器。
最终我们将所有的参数解析器都放在了RequestMappingHandlerAdapter的argumentResolvers中了。
至此,我们的初始化就以及完成了,后面就是请求进来后,找解析器的工作了。
5.使用参数解析器
当前面初始化好了,也可以接受请求的时候,这个时候请求进来了。
根据之前的流程分析,这个请求会从DispatcherSerlvet有的doDispatch一直调用,然后到RequestMappingHandlerAdapter
的invokeHandlerMethod
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
// 设置参数解析器到`ServletInvocableHandlerMethod `
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
// 设置返回值处理器到`ServletInvocableHandlerMethod `
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 调用
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
}
上面的代码可以看到这个参数解析器被设置到了ServletInvocableHandlerMethod
中,这个类我们之前也分析了。
它就是一个可以对这次请求进行参数解析,调用处理方法,最后对返回值解析的类。
然后我们一路跟进到invokeForRequest
,再到getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
在这它用resolvers
变量,这个变量就是之前的RequestMappingHandlerAdapter的argumentResolvers,然后他就调用每个参数解析器的supportsParameter
,判断是否支持,支持了就调用resolveArgument
来获得这个解析后的参数。
所以到这里,我们就分析完成了HandlerMethodArgumentResolver是如何加载,以及如何被使用的。
6.总结
1.SpringBoot加载WebMvcAutoConfiguration
2.调用EnableWebMvcConfiguration,然后调用父类的DelegatingWebMvcConfiguration,将自定义配置WebMvcConfigurer
存放到WebMvcConfigurerComposite
3.调用requestMappingHandlerAdapter,然后调用到顶级父类WebMvcConfigurationSupport的requestMappingHandlerAdapter来创建这个RequestMappingHandlerAdapter
4.调用RequestMappingHandlerAdapter的getArgumentResolvers,然后会调用DelegatingWebMvcConfiguration里面的WebMvcConfigurerComposite,最终回调自定义的WebMvcConfigurer
,将HandlerMethodArgumentResolver
参数解析器加入到RequestMappingHandlerAdapter中
5.请求进来后,从DispatcherSerlvet一直调用到RequestMappingHandlerAdapter的invokeHandlerMethod,
然后创建了一个ServletInvocableHandlerMethod
,并且将WebMvcConfigurerComposite放到它的里面
6.到ServletInvocableHandlerMethod的getMethodArgumentValues里,将处理方法的所以参数都取出来,循环遍历判断参数解析器是否支持,支持的话就解析
7.后续
前面可以看到了这个HandlerMethodArgumentResolver
是有非常多的种类的,后续我们会对这些种类进行一些分析,特别是RequestResponseBodyMethodProcessor,这个是比较复杂的,它不仅把参数解析器实现了,还把返回值处理器也给实现了,内部用HttpMessageConverter
,实现了多种数据转换器,其中内置包含了String,gson,Jackson等等的。