Spring Boot中只能有一个WebMvcConfigurationSupport配置类
首先将结论写文章的最前面,一个项目中只能有一个继承WebMvcConfigurationSupport的@Configuration类(使用@EnableMvc效果相同),如果存在多个这样的类,只有一个配置可以生效。推荐使用
implements WebMvcConfigurer 的方法自定义mvc配置。
背景
项目中的一个模块需要实现上传图片后通过url访问保存在本地上的图片的功能,在SpringBoot 系列教程(十八):SpringBoot通过url访问获取内部或者外部磁盘图片中详细介绍了各种方法,最后我采用了方式三中介绍的直接继承WebMvcConfigurationSupport来实现这一功能。
场景复现
首先按照文章介绍的方法实现配置类
@Configuration
public class ImageConfig extends WebMvcConfigurationSupport {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceLocations("file:./localdata/images/");
}
}
重新启动项目以后尝试访问图片url,但是返回了404错误

经过一番排查,我发现重载的这段方法在Spring Boot启动过程中实际并没有执行,但之前添加的一个跨域的mvc配置却是正确加载了。
这是跨域配置类的实现
@Configuration
public class CorsConfig extends WebMvcConfigurationSupport {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}
两个类都继承了 WebMvcConfigurationSupport 并重写了需要自定义配置的方法,但一个生效了另一个却没有。于是我猜测只能有一个继承 WebMvcConfigurationSupport 的配置类,为了验证我的猜测,我将跨域配置的@Configuration注解删去,只保留ImageConfig的配置,果然可以正常访问图片了!到这里基本可以确定,在Spring Boot的启动过程中,被@Configuration注解的所有类中只有一个WebMvcConfigurationSupport子类的自定义配置可以被正确加载。很容易可以想到,将两段方法写在同一个类中就可以解决这样的问题。那么为什么会出现这样的情况呢?我用类似的关键字搜索,发现同样有人遇到了类似的问题:WebMvcConfigurationSupport没有生效的问题。但是却没有一篇文章讲清楚了原因,于是我决定探索一下其中的奥秘。
原因探索
显然,要找到@Configuration类不能正确加载,就要从Spring Boot如何加载mvc配置入手,但是这方面我不是很了解,于是我在代码中抛出一个throw new NullPointerException();来看以下调用堆栈
org.springframework.beans.factory.BeanCreationException: Error creating bean with
name 'resourceHandlerMapping' defined in class path resource
[test/config/ImageConfig.class]: Bean instantiation via factory method failed; nested
exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate
[org.springframework.web.servlet.HandlerMapping]: Factory method
'resourceHandlerMapping' threw exception; nested exception is
java.lang.NullPointerException
可以看到,是在创建'resourceHandlerMapping'这个bean对象的时候抛出了异常,那么自定义配置的代码也一定是在这个时候被调用的。这个Bean对象正是在被继承的WebMvcConfigurationSupport类中定义的。于是我又打开了相关的源码
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(...) {
...
this.addResourceHandlers(registry);
...
}
}
忽略掉无关的代码,可以看到这个被Bean修饰的方法调用了addResourceHandlers(registry)方法,而这个方法正是继承了这个类后重写的方法,我们用自己自定义的配置重写这个方法就可以改变配置的行为。这样的设计其实就是设计模式中的模板方法模式,在父类中定义方法的框架然后通过钩子函数改变一些特定的步骤。
再回到原本的问题上来,其实当看到这个方法被@Bean修饰之后其实我已经心中又有数了:在Spring Boot中,一个被@Bean修饰的方法在启动过程中会被调用生成Bean对象存放在IoC容器中(前提是这个类本身已经被Spring Boot管理生命周期),也就是说通过继承WebMvcConfigurationSupport自定义的配置方法是在生成父类中定义的@Bean方法时被调用的,而两个配置类中的Bean对象的id是一样的(来自同一个父类相同的方法),也就是说在生成第二个配置类的对象的时候不会再调用其中被@Bean修饰的方法。
整个流程如下:
- 扫描到CorsConfig类,生成Bean对象时调用父类中的被Bean修饰的方法
- 其中某些方法调用了被子类重写的
addCorsMappings(CorsRegistry registry)方法,完成了自定义配置 - 负责管理映射资源的
resourceHandlerMapping方法在此时也被调用了,但是在CorsConfig类中没有对其调用的addResourceHandlers重写,实际上调用了一个空实现。 - 扫描到ImageConfig类,生成Bean对象时发现其中从父类继承的Bean方法已经生成实例了,于是不再调用
resourceHandlerMapping,因此重写的addResourceHandlers方法也就不在有机会运行。
总结
Spring Boot中只能有一个WebMvcConfigurationSupport配置类是真正起作用的,对于这个问题,其实可以通过implements WebMvcConfigurer来解决,多个不同的类实现这个接口后的配置都可以正常运行。
事实上,对于映射资源,Spring Boot的官方文档给出的例子也是通过实现接口完成的。从这次的经历可以看出在写代码的过程中多阅读官方文档可以少走很多弯路,比各类博客文章的教程质量也要更高。另外,在写代码时遇到了问题,除了解决问题本身,了解产生问题的原因也是非常重要的,在这个过程中可以对使用的框架的运行流程更加熟悉。
Spring Boot中只能有一个WebMvcConfigurationSupport配置类的更多相关文章
- 兼容 Spring Boot 1.x 和 2.x 配置类参数绑定的工具类 SpringBootBindUtil
为了让我提供的通用 Mapper 的 boot-starter 同时兼容 Spring Boot 1.x 和 2.x,增加了这么一个工具类. 在 Spring Boot 中,能够直接注入 XXProp ...
- Spring Boot 中 Druid 的监控页面配置
Druid的性能相比HikariCp等其他数据库连接池有一定的差距,但是数据库的相关属性的监控,别的连接池可能还追不上,如图: 今天写一下 Spring Boot 中监控页面的配置,我是直接将seat ...
- 曹工谈Spring Boot:Spring boot中怎么进行外部化配置,一不留神摔一跤;一路debug,原来是我太年轻了
spring boot中怎么进行外部化配置,一不留神摔一跤:一路debug,原来是我太年轻了 背景 我们公司这边,目前都是spring boot项目,没有引入spring cloud config,也 ...
- java中只能有一个实例的类的创建
Java中,如果我们创建一个类,想让这个类只有一个对象,那么我们可以 1:把该类的构造方法设计为private 2:在该类中定义一个static方法,在该方法中创建对象 package test; / ...
- Spring Boot2 系列教程(十八)Spring Boot 中自定义 SpringMVC 配置
用过 Spring Boot 的小伙伴都知道,我们只需要在项目中引入 spring-boot-starter-web 依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比 ...
- Spring Boot中的缓存支持(一)注解配置与EhCache使用
Spring Boot中的缓存支持(一)注解配置与EhCache使用 随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决 ...
- Spring Boot 中自定义 SpringMVC 配置,到底继承谁哪一个类或则接口?
看了这篇文章,写的非常的言简意赅,特此记录下: 1.Spring Boot 1.x 中,自定义 SpringMVC 配置可以通过继承 WebMvcConfigurerAdapter 来实现. 2.Sp ...
- Spring Boot中使用EhCache实现缓存支持
SpringBoot提供数据缓存功能的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存.,相信非常多人已经用过cache了.因为数据库的IO瓶颈.一般情况下我们都会引入非常多的缓存策略, ...
- Spring Boot中使用MongoDB数据库
前段时间分享了关于Spring Boot中使用Redis的文章,除了Redis之后,我们在互联网产品中还经常会用到另外一款著名的NoSQL数据库MongoDB. 下面就来简单介绍一下MongoDB,并 ...
随机推荐
- 模块 schedule 定时任务
schedule模块实现定时任务 2018-08-29 15:01:51 更多 一.官方示例 import schedule import time def job(): print("I' ...
- [vijos1162]波浪数
题目链接:https://www.vijos.org/p/1162 这题的解法我觉得可能是模拟吧,但是题的分类又是构造QAQ..... 不是很懂,所以我们把这个方法叫做奇技淫巧吧 这题的暴力思路就是针 ...
- 求组合数m_n
下面为求取组合数的代码: #include <stdio.h> #define MAX 10009 ]; void print(int *v, int length) { ; for (; ...
- pycharm 秘籍:快捷键技巧等
Pycharm基本使用 安装 下载地址:https://www.jetbrains.com/pycharm/download 选择Professional 专业版 Comunnity社区版是免费的,但 ...
- Golang 实现 Redis(4): AOF 持久化与AOF重写
本文是使用 golang 实现 redis 系列的第四篇文章,将介绍如何使用 golang 实现 Append Only File 持久化及 AOF 文件重写. 本文完整源代码在作者GithubHDT ...
- Spring之Bean的管理方式(Content,Beans)
Spring的bean管理(注释) 注解 代码里特殊的标记,使用注解也可以直接完成相关功能 注解写法:@注解名称(属性名=属性值) 使用在类,方法,属性上面 Spring注解开发准备 导入jar包 ( ...
- 关于竞赛大佬常用的 static const auto _ = []() 用法解析
前言 在刷Leetcode的时候发现很多运算速度极快的代码都有这一段,所以研究一下. static const auto _ = []() { ios::sync_with_stdio(false); ...
- 「给产品经理讲JVM」:垃圾收集算法
纠结的我,给我的JVM系列终于起了第三个名字,害,我真是太难了.从 JVM 到 每日五分钟,玩转 JVM 再到现在的给产品经理讲 JVM ,虽然内容为王,但是标题可以让更多的人看到我的文章,所以,历经 ...
- python_Mock基本使用
## 1.mock简介 1. py3已将mock集成到unittest库中 2. 为的就是更好的进行单元测试 3. 简单理解,模拟接口返回参数 4. 通俗易懂,直接修改接口返回参数的值 5. 官方文档 ...
- Java第十二天,权限修饰符
Java当中权限修饰符共有四种.分别是public.protected.(default).private. 注:YSE代表可访问,NO代表不可访问. 同一个类 同一个包,非继承 不同的包,有继承 ...