阅读完需:约 11 分钟
用过 Spring Boot 的人都知道,我们只需要在项目中引入 spring-boot-starter-web
依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比较复杂,系统自带的配置不一定满足我们的需求,往往我们还需要结合实际情况自定义配置。
自定义配置就有讲究了,由于 Spring Boot 的版本变迁,加上这一块本身就有几个不同写法,很多小伙伴在这里容易搞混,今天松哥就来和大家说一说这个问题。
概览
首先我们需要明确,跟自定义 SpringMVC 相关的类和注解主要有如下四个:
WebMvcConfigurerAdapter
WebMvcConfigurer
WebMvcConfigurationSupport
@EnableWebMvc
这四个中,除了第四个是注解,另外三个两个类一个接口,里边的方法看起来好像都类似,但是实际使用效果却大不相同,因此很多小伙伴容易搞混,今天松哥就来和大家聊一聊这个问题。
WebMvcConfigurerAdapter
我们先来看 WebMvcConfigurerAdapter
,这个是在 Spring Boot 1.x 中我们自定义 SpringMVC 时继承的一个抽象类,这个抽象类本身是实现了 WebMvcConfigurer 接口,然后抽象类里边都是空方法,我们来看一下这个类的声明:
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
//各种 SpringMVC 配置的方法
}
再来看看这个类的注释:
/**
* An implementation of {@link WebMvcConfigurer} with empty methods allowing
* subclasses to override only the methods they're interested in.
* @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
*/
这段注释关于这个类说的很明白了。同时我们也看到,从 Spring5 开始,由于我们要使用 Java8,而 Java8 中的接口允许存在 default 方法,因此官方建议我们直接实现 WebMvcConfigurer 接口,而不是继承 WebMvcConfigurerAdapter 。
也就是说,在 Spring Boot 1.x 的时代,如果我们需要自定义 SpringMVC 配置,直接继承 WebMvcConfigurerAdapter 类即可。
WebMvcConfigurer
根据上一小节的解释,小伙伴们已经明白了,WebMvcConfigurer 是我们在 Spring Boot 2.x 中实现自定义配置的方案。
WebMvcConfigurer 是一个接口,接口中的方法和 WebMvcConfigurerAdapter 中定义的空方法其实一样,所以用法上来说,基本上没有差别,从 Spring Boot 1.x 切换到 Spring Boot 2.x ,只需要把继承类改成实现接口即可。
凡是涉及到自定义 SpringMVC 配置的地方,也都是通过实现 WebMvcConfigurer 接口来完成的。
WebMvcConfigurationSupport
前面两个都好理解,还有一个 WebMvcConfigurationSupport
,这个又是干什么用的呢?
之前有文章中用过这个类,就是下面这篇:
这篇文章放弃了 Spring 和 SpringMVC 的 xml 配置文件,转而用 Java 代替这两个 xml 配置。那么在这里我自定义 SpringMVC 配置的时候,就是通过继承 WebMvcConfigurationSupport
类来实现的。在 WebMvcConfigurationSupport
类中,提供了用 Java 配置 SpringMVC 所需要的所有方法。我们来看一下这个方法的摘要:
有一点眼熟,可能有人发现了,这里的方法其实和前面两个类中的方法基本是一样的。
在这里首先大家需要明确的是,WebMvcConfigurationSupport
类本身是没有问题的,我们自定义 SpringMVC 的配置是可以通过继承 WebMvcConfigurationSupport
来实现的。但是继承 WebMvcConfigurationSupport
这种操作我们一般只在 Java 配置的 SSM 项目中使用,Spring Boot 中基本上不会这么写,为什么呢?
Spring Boot 中,SpringMVC 相关的自动化配置是在 WebMvcAutoConfiguration
配置类中实现的,那么我们来看看这个配置类的生效条件:
@Configuration
@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 {
}
我们从这个类的注解中可以看到,它的生效条件有一条,就是当不存在 WebMvcConfigurationSupport 的实例时,这个自动化配置才会生生效。因此,如果我们在 Spring Boot 中自定义 SpringMVC 配置时选择了继承 WebMvcConfigurationSupport,就会导致 Spring Boot 中 SpringMVC 的自动化配置失效。
Spring Boot 给我们提供了很多自动化配置,很多时候当我们修改这些配置的时候,并不是要全盘否定 Spring Boot 提供的自动化配置,我们可能只是针对某一个配置做出修改,其他的配置还是按照 Spring Boot 默认的自动化配置来,而继承 WebMvcConfigurationSupport 来实现对 SpringMVC 的配置会导致所有的 SpringMVC 自动化配置失效,因此,一般情况下我们不选择这种方案。
在 Java 搭建的 SSM 项目中(纯 Java 代码搭建 SSM 环境),因为本身就没什么自动化配置,所以我们使用了继承 WebMvcConfigurationSupport
。
@EnableWebMvc
最后还有一个 @EnableWebMvc 注解,这个注解很好理解,它的作用就是启用 WebMvcConfigurationSupport
。我们来看看这个注解的定义:
/**
* Adding this annotation to an {@code @Configuration} class imports the Spring MVC
* configuration from {@link WebMvcConfigurationSupport}, e.g.:
可以看到,加了这个注解,就会自动导入 WebMvcConfigurationSupport
,所以在 Spring Boot 中,我们也不建议使用 @EnableWebMvc
注解,因为它一样会导致 Spring Boot 中的 SpringMVC 自动化配置失效。
总结
- Spring Boot 1.x 中,自定义 SpringMVC 配置可以通过继承 WebMvcConfigurerAdapter 来实现。
- Spring Boot 2.x 中,自定义 SpringMVC 配置可以通过实现 WebMvcConfigurer 接口来完成。
- 如果在 Spring Boot 中使用继承 WebMvcConfigurationSupport 来实现自定义 SpringMVC 配置,或者在 Spring Boot 中使用了 @EnableWebMvc 注解,都会导致 Spring Boot 中默认的 SpringMVC 自动化配置失效。
- 在纯 Java 配置的 SSM 环境中,如果我们要自定义 SpringMVC 配置,有两种办法,第一种就是直接继承自 WebMvcConfigurationSupport 来完成 SpringMVC 配置,还有一种方案就是实现 WebMvcConfigurer 接口来完成自定义 SpringMVC 配置,换句话说,在纯 Java 配置的 SSM 中,如果你需要自定义 SpringMVC 配置,你离不开 WebMvcConfigurationSupport ,所以在这种情况下建议通过继承 WebMvcConfigurationSupport 来实现自动化配置。
目前的SpringBoot版本是2.6.11
- 实现
WebMvcConfigurer
: 不会覆盖WebMvcAutoConfiguration
的配置 - 实现
WebMvcConfigurer
+注解@EnableWebMvc
:会覆盖WebMvcAutoConfiguration
的配置 - 继承
WebMvcConfigurationSupport
:会覆盖WebMvcAutoConfiguration
的配置 - 继承
DelegatingWebMvcConfiguration
:会覆盖WebMvcAutoConfiguration
的配置
-
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}),
当Spring容器中不存在WebMvcConfigurationSupportbean
,WebMvcAutoConfiguration
才会注入 - 如果有配置文件继承了
DelegatingWebMvcConfiguration
,或者WebMvcConfigurationSupport
,或者配置类注解了@EnableWebMvc
,那么WebMvcAutoConfiguration
将不会被自动配置,而是使用WebMvcConfigurationSupport
的配置。那么!!!!所有实现了WebMvcConfigurer
的配置类有可能会 全部失效!!!!!
- Spring Boot 默认提供Spring MVC 自动配置,不需要使用@EnableWebMvc注解
- 如果需要配置MVC(拦截器、格式化、视图等)请使用添加@Configuration并实现WebMvcConfigurer接口.不要添加@EnableWebMvc注解。
- @EnableWebMvc 只能添加到一个@Configuration配置类上,用于导入Spring Web MVC
那WebMvcConfigurer
如何被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的子类。
@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
来进行参数的解析。