Spring Boot 2.1 開始,
spring.main.allow-bean-definition-overriding預設值被改為false。當同一個 Spring IoC 容器內有兩個不同來源的 Bean 註冊了完全相同的 Bean 名稱時,Spring 不再預設後註冊覆蓋先註冊,而是立即拋出BeanDefinitionOverrideException終止啟動,以避免因加載順序不穩定導致的幽靈 Bug。
Issue & Env(案發現場與環境)
- JDK 版本:OpenJDK 17 / 21
- Spring Boot 版本:2.7.x / 3.x
- 觸發背景:升級 Spring Boot 版本、引入二方/三方 Starter、或多模塊下同時存在同名元件定義。
啟動日誌中拋出 APPLICATION FAILED TO START 提示,指出容器內同名 Bean 衝突且覆蓋被禁用。
StackTrace(堆疊日誌)
2026-09-04 15:20:11.233 ERROR 71204 --- [main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'userService', defined in class path resource [com/example/config/UserConfig.class], could not be registered. A bean with that name has already been defined in file [/Users/demo/target/classes/com/example/service/UserService.class] and overriding is disabled.
Action:
Consider renaming one of the beans or being more specific about which bean should be used by setting 'spring.main.allow-bean-definition-overriding=true'
org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'userService' defined in class path resource [com/example/config/UserConfig.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=userConfig; factoryMethodName=userService; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/example/config/UserConfig.class]] for bean 'userService': There is already [Generic bean: class [com.example.service.UserService]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/demo/target/classes/com/example/service/UserService.class]] bound.
at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:1016)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:295)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:153)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:102)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343)
Reproduce(復現程式碼)
在項目中同時存在類級別元件掃描和設定類 @Bean 方法,且兩者的 Bean 預設名稱同為 userService:
// 第一处:通过 @Service 注解声明组件,默认 Bean Name 为 "userService"
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void doWork() {}
}
// 第二处:外部配置模块或公共工具库里显式声明了同名 @Bean
package com.example.config;
import com.example.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
當 Spring 執行 ConfigurationClassPostProcessor 處理 Bean 定義註冊時,由於名稱重複且預設禁止覆蓋,拋出上述例外。
Root Cause(根因分析)
在 Spring 底層,DefaultListableBeanFactory 負責維護 Bean 名稱到 BeanDefinition 的映射表 beanDefinitionMap:
- 歷史行為(Spring Framework 原生預設):早期版本的 Spring 中,
allowBeanDefinitionOverriding預設為true。若存在重名 Bean,後掃描到的定義會默默頂替先掃描到的定義。然而在複雜的微服務或多模塊應用中,不同模塊在不同操作系統或不同打包順序下,掃描順序可能發生微小差異,導致偶發性生產故障。 - Spring Boot 2.1+ 的策略轉變:Spring Boot 官方團隊在 2.1.0 決定預設將
allowBeanDefinitionOverriding設置為false,強行執行快速失敗(Fail-Fast)策略。 - 衝突源識別:常見衝突包括:
- 業務開發時手寫
@Service類,又在@Configuration設定類中寫了同名@Bean方法。 - 引入的第三方 Starter 或舊版 SDK 內部寫死了固定 Bean 名稱,與業務主工程衝突。
- Feign 客戶端開啟了多個接口指向同一個服務名,未設定獨立 contextId。
- 業務開發時手寫
The Fix(解決方案)
方案 1(最佳實踐):重命名或使用條件化註解
如果是工程自身業務程式碼,明確職責邊界,避免同名定義。
如果是自定義 Starter 或通用庫,應使用 @ConditionalOnMissingBean 保證業務側可以優先定製:
@Configuration
public class UserConfig {
@Bean
@ConditionalOnMissingBean(UserService.class)
public UserService userService() {
return new UserService();
}
}
如果是 Spring Cloud OpenFeign 接口同名衝突,可以在 @FeignClient 中補充 contextId:
@FeignClient(value = "order-service", contextId = "orderUserClient")
public interface OrderUserClient { ... }
方案 2(緊急止血):開啟全局允許覆蓋設定
在項目緊急發版或老舊項目多方依賴一時難以修改時,可在 application.yml 中開啟覆蓋開關作為過渡方案:
spring:
main:
allow-bean-definition-overriding: true
警示:開啟此開關會重新恢復“後註冊覆蓋先註冊”的不穩定行為,可能導致環境之間表現不一致。僅建議在重構週期短缺時臨時止血,後續仍應消除同名衝突。
