User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Spring   ›   SpringBoot   ›   正文
SpringBoot

Spring Boot 中自定义 SpringMVC 配置,到底继承谁?

2020-04-06 01:39:45
1366  0 2
参考目录 隐藏
1) 概览
2) WebMvcConfigurerAdapter
3) WebMvcConfigurer
4) WebMvcConfigurationSupport
5) @EnableWebMvc
6) 总结
7) 那WebMvcConfigurer如何被Spring调用到的呢

阅读完需:约 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 接口来完成的。

SpringBoot—WebMvcConfigurer详解

WebMvcConfigurationSupport

前面两个都好理解,还有一个 WebMvcConfigurationSupport ,这个又是干什么用的呢?

之前有文章中用过这个类,就是下面这篇:

纯 Java 搭建 SSM 环境(2)

这篇文章放弃了 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 自动化配置失效。

总结

  1. Spring Boot 1.x 中,自定义 SpringMVC 配置可以通过继承 WebMvcConfigurerAdapter 来实现。
  2. Spring Boot 2.x 中,自定义 SpringMVC 配置可以通过实现 WebMvcConfigurer 接口来完成。
  3. 如果在 Spring Boot 中使用继承 WebMvcConfigurationSupport 来实现自定义 SpringMVC 配置,或者在 Spring Boot 中使用了 @EnableWebMvc 注解,都会导致 Spring Boot 中默认的 SpringMVC 自动化配置失效。
  4. 在纯 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来进行参数的解析。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

2 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593219浏览

随机文章
Java 关于时间的操作
5年前
Kotlin-类型进阶—内联类(二十七)
4年前
SpringCloud—OpenFeign(三)继承特性
5年前
WebSocket(二)
5年前
HttpServletRequest常用的方法
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1926 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593219 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付