StackTraces
Spring Boot已驗證

Spring Boot 報 BeanDefinitionOverrideException

@debug_master27 天前更新閱讀 6 min00

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:

  1. 歷史行為(Spring Framework 原生預設):早期版本的 Spring 中,allowBeanDefinitionOverriding 預設為 true。若存在重名 Bean,後掃描到的定義會默默頂替先掃描到的定義。然而在複雜的微服務或多模塊應用中,不同模塊在不同操作系統或不同打包順序下,掃描順序可能發生微小差異,導致偶發性生產故障。
  2. Spring Boot 2.1+ 的策略轉變:Spring Boot 官方團隊在 2.1.0 決定預設將 allowBeanDefinitionOverriding 設置為 false,強行執行快速失敗(Fail-Fast)策略。
  3. 衝突源識別:常見衝突包括:
    • 業務開發時手寫 @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

警示:開啟此開關會重新恢復“後註冊覆蓋先註冊”的不穩定行為,可能導致環境之間表現不一致。僅建議在重構週期短缺時臨時止血,後續仍應消除同名衝突。

References(參考來源)

最後更新於 2026-09-14

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法