Spring Boot 3.1中spring.main.allow-bean-definition-overriding为何未生效?
问题背景
我有一个自定义Spring Boot Starter,在测试该Starter时需要覆盖其中的MyWebClientFilters Bean,想用Mock实例替代真实实现。查阅资料后配置了spring.main.allow-bean-definition-overriding: true,但测试时依然使用了真实的Bean。使用的Spring Boot版本为3.1。
应用代码
@AutoConfiguration class MyConfig { @Bean MyWebClientFilters myWebClientFilters() { ... } @Bean @Qualifier("myWebClient") WebClient myWebClient(WebClient.Builder builder, MyWebClientFilters myWebClientFilters) { return builder .filter(myWebClientFilters.attachSomeCustomHeader()) .build() } }
测试代码
@SpringBootTest class MyTest { @TestConfiguration class TestConfig { // 设为static也无效 @Bean //@Primary 添加该注解同样无效 MyWebClientFilters myWebClientFilters() { var mock = Mockito.mock(MyWebClientFilters.class); // 配置mock return mock; } } @Autowired @Qualifier("myWebClient") WebClient webClientUnderTest; @Test void test_it() { var res = webClientUnderTest.get().retrieve().bodyToMono(String.class).block(); } }
问题核心
配置了spring.main.allow-bean-definition-overriding: true,且在测试类中通过@TestConfiguration提供了Mock的MyWebClientFilters Bean,甚至尝试过添加@Primary注解、将TestConfig设为static,但测试时WebClient依然使用真实的MyWebClientFilters实例,这是为什么?
原因分析
问题出在Bean的加载和初始化顺序:
- Spring Boot的自动配置类(
@AutoConfiguration标记的MyConfig)优先级远高于测试配置类(@TestConfiguration),会先完成Bean的注册与初始化。 WebClientBean依赖MyWebClientFilters,所以在自动配置阶段,真实的MyWebClientFilters实例会被创建并注入到WebClient中,此时WebClient已经完成初始化。- 后续测试配置的Mock Bean虽然覆盖了容器中的
MyWebClientFilters,但WebClient已经持有了之前的真实实例,不会自动重新注入或刷新。
另外,@Primary注解在这里无效,因为它的作用是当存在多个同类型Bean时优先选择,但这里的问题是Bean初始化顺序导致依赖注入发生在覆盖之前,而非多个Bean共存时的选择问题。
解决办法
方法一:让测试配置优先加载
通过@AutoConfigureBefore注解,强制测试配置类在目标自动配置类之前加载,这样Mock的MyWebClientFilters会先被注册,WebClient初始化时就会注入Mock实例:
@SpringBootTest @AutoConfigureBefore(MyConfig.class) class MyTest { @TestConfiguration static class TestConfig { // 建议设为static避免内部类依赖外部实例 @Bean MyWebClientFilters myWebClientFilters() { var mock = Mockito.mock(MyWebClientFilters.class); // 配置mock return mock; } } // 其他测试代码不变 }
方法二:给自动配置Bean添加缺失条件
在Starter的自动配置类中,给myWebClientFilters()方法添加@ConditionalOnMissingBean注解,这样当容器中已经存在MyWebClientFilters类型的Bean时(比如测试中的Mock Bean),自动配置的真实Bean就不会被注册,无需开启Bean覆盖:
@AutoConfiguration class MyConfig { @Bean @ConditionalOnMissingBean MyWebClientFilters myWebClientFilters() { ... } // WebClient Bean代码不变 }
这种方式更符合Spring Boot自动配置的设计理念,推荐在自定义Starter时使用。
方法三:使用@MockBean(最简便)
Spring Boot测试提供了@MockBean注解,可以直接替换容器中的指定Bean,无需手动编写TestConfig和配置Bean覆盖:
@SpringBootTest class MyTest { @MockBean MyWebClientFilters myWebClientFilters; @Autowired @Qualifier("myWebClient") WebClient webClientUnderTest; @BeforeEach void setup() { // 配置mock行为,比如when(myWebClientFilters.attachSomeCustomHeader()).thenReturn(...) } @Test void test_it() { var res = webClientUnderTest.get().retrieve().bodyToMono(String.class).block(); } }
这是Spring Boot测试中替换Bean的推荐方式,简洁且不易出错。
内容的提问来源于stack exchange,提问作者evkaky

