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

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法