WebMvcConfigurer配置类其实是Spring的Java-based配置方式的一种体现,采用JavaBean的形式来代替传统的xml配置文件形式针对框架进行个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。
定制Spring MVC配置,需要创建一个配置类并实现WebMvcConfigurer 接口。Spring5.0之前的版本,Spring推荐的是通过继承WebMvcConfigurerAdapter的方式来扩展mvc相关配置,Sping5.0之后,由于JDK8中接口支持默认方法的新特性 ,WebMvcConfigurerAdapter已被标记为Depercated,可直接实现WebMvcConfigurer。
我们先看下两个类的关系和注释:
/**
* Defines callback methods to customize the Java-based configuration for
* Spring MVC enabled via {@code @EnableWebMvc}.
*
* <p>{@code @EnableWebMvc}-annotated configuration classes may implement
* this interface to be called back and given a chance to customize the
* default configuration.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author David Syer
* @since 3.1
*/
public interface WebMvcConfigurer {
/**
* 一个WebMvcConfigurer的空实现类,允许子类重写它们感兴趣的方法(来进行配置扩展)
* An implementation of {@link WebMvcConfigurer} with empty methods allowing
* subclasses to override only the methods they're interested in.
*
* @author Rossen Stoyanchev
* @since 3.1
* 从5.0版本标记为过时。是由于WebMvcConfigurer接口类(基于Java 8版本)拥有了默认的方法,可以被直接实现而不需要通过
* WebMvcConfigurerAdapter类进行中间的适配。
* @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made
* possible by a Java 8 baseline) and can be implemented directly without the
* need for this adapter
*/
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
从类的注释我们可以看出,从Spring5.0版本,我们可以直接实现WebMvcConfigurer而不需要通过继承WebMvcConfigurerAdapter 的方式。
那么,为何通过实现WebMvcConfigurer接口就可以自定义或者扩展mvc的配置呢
下面我们以WebMvcConfigurer的addArgumentResolvers方法为例来分析Spring是如何读取并使用我们自定义的参数解析器的。
WebMvcConfigurer的addArgumentResolvers方法定义
/**
* 新增一个解析器以支持自定义的Controller方法的参数类型
* Add resolvers to support custom controller method argument types.
* 这个方法不会覆盖内置的参数解析器,如果要修改内置的参数解析器,需要直接继承RequestMappingHandlerAdapter类,
* <p>This does not override the built-in support for resolving handler
* method arguments. To customize the built-in support for argument
* resolution, configure {@link RequestMappingHandlerAdapter} directly.
* @param resolvers initially an empty list
*/
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
那么我们重写的这个方法是如何被Spring调用到的呢?
我们直接打开addArgumentResolvers方法的调用关系,发现被三个类调用,无论打开哪个类都可以找到最终调用的地方
我们打开DelegatingWebMvcConfiguration这个类,这个类是WebMvcConfigurer 的代理类,发现它的addArgumentResolvers方法是调用了成员变量configurers的addArgumentResolvers方法,查看这个成员变量,发现这个变量是通过@Autowired注解持有了所有自定义WebMvcConfigurer的子类。
到了这里,我们看到了我们自定义的WebMvcConfigurer的子类是被DelegatingWebMvcConfiguration类通过注入的方法读取到的,由于使用的是@Autowired的方式,所以要求我们自定义的类需要使用@Configuration或者@Bean、或者@Component等注解,保证这个类被Spring容器管理才可以生效。
DelegatingWebMvcConfiguration的addArgumentResolvers方法
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
DelegatingWebMvcConfiguration中的configurers 成员变量,是通过@Autowired注解持有了所有自定义的WebMvcConfigurer的子类。
/**
* A subclass of {@code WebMvcConfigurationSupport} that detects and delegates
* to all beans of type {@link WebMvcConfigurer} allowing them to customize the
* configuration provided by {@code WebMvcConfigurationSupport}. This is the
* class actually imported by {@link EnableWebMvc @EnableWebMvc}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//这个成员变量是通过下面的@Autowired注解持有了所有自定义的WebMvcConfigurer的子类。
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//关键逻辑:此处通过@Autowire注解实现了所有的自定义WebMvcConfigurer子类的注入,
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
现在我们找到了Spring是如何读取自定义的WebMvcConfigurer 的子类的,那么Spring是在哪个地方使用的呢?
继续查看DelegatingWebMvcConfiguration 的addArgumentResolvers方法的调用链,我们发现是被WebMvcConfigurationSupport的addArgumentResolvers方法调用的,这个方法又被类内部的addDefaultHandlerExceptionResolvers方法和requestMappingHandlerAdapter方法调用,这个两个方法分别是用来初始化SpringBoot的默认异常处理解析器ExceptionHandlerExceptionResolver和默认的请求映射处理适配器RequestMappingHandlerAdapter,分别打开两个方法,我们发现我们自定义的ArgumentResolvers被分别设置到了ExceptionHandlerExceptionResolver类和RequestMappingHandlerAdapter类中。
WebMvcConfigurationSupport的addDefaultHandlerExceptionResolvers方法
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
//设置自定义的参数解析器(通过重写WebMvcConfigurer的addArgumentResolvers方法)
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers())
WebMvcConfigurationSupport的requestMappingHandlerAdapter方法
/**
* 返回一个RequestMappingHandlerAdapter,用来处理Controller中使用了注解@RequestMapping的方法对应的请求
* Returns a {@link RequestMappingHandlerAdapter} for processing requests
* through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
* <ul>
* addArgumentResolvers方法用来新增自定义的参数解析器
* <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
* addReturnValueHandlers方法用来新增自定义的返回值处理器
* <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
* configureMessageConverters方法用来新增自定义的消息转换器
* <li>{@link #configureMessageConverters} for adding custom message converters.
* </ul>
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
//创建一个默认配置的RequestMappingHandlerAdapter
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
//设置自定义的参数解析器(通过重写WebMvcConfigurer的addArgumentResolvers方法)
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
至此,我们找到了WebMvcConfigurer子类重写的方法是如何被SpringBoot读取并且使用的,通过这个方式,可以分别找到WebMvcConfigurer类其它的方法的读取和使用的逻辑。
后续Spring会在使用RequestMappingHandlerAdapter处理http请求或者使用ExceptionHandlerExceptionResolver 进行异常处理的时候,使用到我们自定义的ArgumentResolvers来进行参数的解析,如果对后续步骤感兴趣的话,可以去查看这两个类的源码。
通过查看方法调用链来定位和梳理逻辑是阅读源码的一种方式,可以帮助我们快速查找和分析源码,如果有更好的方式,欢迎评论或私信交流。
另外需要注意的是:
1、实现了WebMvcConfigruer的自定义类,一定要加上@Configuration或者@Component注解,保证这个类被Spring容器管理才可以生效。
2、定制SpringMVC的配置要通过实现WebMvcConfigurer接口的方式,而不能继承WebMvcConfigruerSupport,因为一旦继承了WebMvcConfigruerSupport,会导致MVC的自动配置类WebMvcAutoConfiguration失效,产生不必要的问题。
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//如果容器中存在WebMvcConfigurationSupport类型的Bean,则WebMvcAutoConfiguration类不生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
可以看到,一旦容器中存在WebMvcConfigurationSupport类型的Bean,WebMvcAutoConfiguration类就不会生效。
本文为个人学习整理,如有描述错误或者对相关内容感兴趣,欢迎评论或私信交流,一起讨论、共同进步。