Spring中是否可以存在两个相同ID的bean

news2024/11/28 7:42:02

文章目录

  • 一、在同一个xml配置文件里配置两个相同ID的bean
    • 结论
    • 验证过程
    • 源码
  • 二、在不同xml配置文件里配置两个相同ID的bean
    • 结论
    • 验证过程
    • 源码
  • 三、在同一个配置类中以@Bean方式添加两个名称相同的bean
    • 结论
    • 验证过程
    • 源码
  • 四、在不同配置类中以@Bean方式添加两个名称相同的bean
    • 结论
    • 验证过程
    • 源码
  • 五、以@Component的方式添加两个名称相同的bean
    • 结论
    • 验证过程
    • 源码
  • 总结

一、在同一个xml配置文件里配置两个相同ID的bean

结论

如果在同一个 xml 配置文件里配置两个相同 ID 的 bean,在 Spring 容器启动时会报错

验证过程

resources 目录下增加 beans.xml 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="demoService" class="com.victor.demo.service.DemoService01"/>

    <bean id="demoService" class="com.victor.demo.service.DemoService02"/>
</beans>

在启动类上增加注解 @ImportResource("classpath:beans.xml")

@SpringBootApplication
@ImportResource("classpath:beans.xml")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

在 service 目录下添加两个类,DemoService01DemoService02,就不贴代码了,空类就可以

然后启动,会报如下错

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean name ‘demoService’ is already used in this element
Offending resource: class path resource [beans.xml]

源码

原因是,Spring 在解析 xml 文件的时候,会校验 bean 的 ID 唯一性,具体代码在 BeanDefinitionParserDelegatecheckNameUniqueness 方法里

//BeanDefinitionParserDelegate
/**
 * Validate that the specified bean name and aliases have not been used already
 * within the current level of beans element nesting.
 */
protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
   String foundName = null;

   if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
      foundName = beanName;
   }
   if (foundName == null) {
      foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
   }
   if (foundName != null) {
      error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
   }

   this.usedNames.add(beanName);
   this.usedNames.addAll(aliases);
}

这里会维护一个叫 usedNamesHashSet 集合,记录已经使用的 beanName,也就是 ID

二、在不同xml配置文件里配置两个相同ID的bean

结论

如果在不同 xml 配置文件里配置两个相同 ID 的 bean,在 Spring 容器启动时会报错,但是增加 allow-bean-definition-overriding=true 参数后,后加载的会覆盖先加载的

验证过程

resources 目录下增加 beans.xmlbeans2.xml 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="demoService" class="com.victor.demo.service.DemoService01"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="demoService" class="com.victor.demo.service.DemoService02"/>
</beans>

在启动类上增加注解 @ImportResource({"classpath:beans.xml", "classpath:beans2.xml"})

@SpringBootApplication
@ImportResource({"classpath:beans.xml", "classpath:beans2.xml"})
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

DemoService01DemoService02 两个类保持不变

启动,报错如下:

Description:

The bean ‘demoService’, defined in class path resource [beans2.xml], could not be registered. A bean with that name has already been defined in class path resource [beans.xml] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

其实报错信息里提示我们了,把参数 spring.main.allow-bean-definition-overriding 设置成 true,好,我们去设置一下,在 application.yml 文件里添加

spring:
    main:
        allow-bean-definition-overriding: true

然后把启动类改下,获取下 demoService,打印一下

@SpringBootApplication
@ImportResource({"classpath:beans.xml", "classpath:beans2.xml"})
public class DemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
        System.out.println(applicationContext.getBean("demoService"));
    }

}

启动,控制台打印如下:

com.victor.demo.service.DemoService02@5dfe23e8

可以看到确实是覆盖了

源码

原因是 Spring 在注册 BeanDefinition 的时候,会比对已经注册的 BeanDefinition,然后通过参数 allowBeanDefinitionOverriding 判断是否覆盖,如果配置为 true,就覆盖,如果是 false,就抛异常,具体代码在 DefaultListableBeanFactoryregisterBeanDefinition 方法里

//DefaultListableBeanFactory
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

   Assert.hasText(beanName, "Bean name must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");

   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Validation of bean definition failed", ex);
      }
   }

   //从已经注册的 beanDefinition 中获取,如果获取到,就说明已经存在
   BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
   if (existingDefinition != null) {
      //判断 allowBeanDefinitionOverriding 参数是否是 true
      if (!isAllowBeanDefinitionOverriding()) {
         //如果是 false,就抛出 BeanDefinitionOverrideException 异常
         throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
      }
      else if (existingDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  existingDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(existingDefinition)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            removeManualSingletonName(beanName);
         }
      }
      else {
         // Still in startup registration phase
         this.beanDefinitionMap.put(beanName, beanDefinition);
         this.beanDefinitionNames.add(beanName);
         removeManualSingletonName(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (existingDefinition != null || containsSingleton(beanName)) {
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}

通过 BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); 从已经注册的 beanDefinition 中获取,如果获取到,就说明已经存在,然后判断 allowBeanDefinitionOverriding 参数是否是 true,如果是 false,就抛出 BeanDefinitionOverrideException 异常

三、在同一个配置类中以@Bean方式添加两个名称相同的bean

结论

如果在同一个配置类中以@Bean方式添加两个名称相同的bean,会注册第一个bean

验证过程

添加配置类 DemoAutoConfiguration

@Configuration
public class DemoAutoConfiguration {

    @Bean("demoService")
    public DemoService01 demoService01() {
        return new DemoService01();
    }

    @Bean("demoService")
    public DemoService02 demoService02() {
        return new DemoService02();
    }
}

启动,控制台打印如下:

com.victor.demo.service.DemoService01@682abca7

可以看到确实注册了第一个

源码

关于 @Configuration 注解和 @Bean 注解解析的源码在 ConfigurationClassPostProcessor 这个BeanDefinitionRegistryPostProcessor 里(关于 BeanDefinitionRegistryPostProcessor 这里就不讲了),重点在 processConfigBeanDefinitions 方法里

image-20230925140132686

下面这一步具体会在 ConfigurationClassBeanDefinitionReader 类的 loadBeanDefinitionsForConfigurationClass 方法里

image-20230925140327280

然后看看这个 loadBeanDefinitionsForBeanMethod 方法

image-20230925140434454

这里有一个 isOverriddenByExistingDefinition 方法,判断是否被已存在的 BeanDefinition 覆盖,如果返回 true,就被已存在的 BeanDefinition 覆盖,也就是当前这个不注册,直接return,具体看看 isOverriddenByExistingDefinition 方法

image-20230925141618510

先通过 if (existingBeanDef instanceof ConfigurationClassBeanDefinition) 看 bean 是不是配置类创建的,如果是就通过 if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName())) 判断配置类是不是同一个,如果是同一个配置类,就被已存在的覆盖,也就是只注册第一个,如果配置类不是同一个呢,这里是会通过了,但后面呢?再来试试

四、在不同配置类中以@Bean方式添加两个名称相同的bean

结论

如果在不同配置类中以@Bean方式添加两个名称相同的bean,在 Spring 容器启动时会报错,但是增加 allow-bean-definition-overriding=true 参数后,后加载的会覆盖先加载的

验证过程

添加配置类 DemoAutoConfigurationDemoAutoConfiguration2

@Configuration
public class DemoAutoConfiguration {

    @Bean("demoService")
    public DemoService01 demoService01() {
        return new DemoService01();
    }
}
@Configuration
public class DemoAutoConfiguration2 {

    @Bean("demoService")
    public DemoService02 demoService02() {
        return new DemoService02();
    }
}

启动,报错如下:

Description:

The bean ‘demoService’, defined in class path resource [com/victor/demo/config/DemoAutoConfiguration2.class], could not be registered. A bean with that name has already been defined in class path resource [com/victor/demo/config/DemoAutoConfiguration.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

和 **二、在不同 xml 配置文件里配置两个相同ID的bean **里差不多的提示,我们在 application.yml 文件里添加配置

spring:
    main:
        allow-bean-definition-overriding: true

启动,控制台打印如下:

com.victor.demo.service.DemoService02@2539cd1c

可以看到确实是覆盖了

源码

其实这里我们不看源码也可以知道了,虽然在 三、在同一个配置类中以@Bean方式添加两个名称相同的bean 源码分析里,那边没有return,但是到注册 BeanDefinition 的时候,还是会校验,就像 **二、在不同 xml 配置文件里配置两个相同ID的bean **那样,也可以理解,本身 Spring 推出 @Configuration + @Bean 的方式就是为了取代 xml 配置的方式,不同的配置类就像是不同的 xml 配置文件一样

五、以@Component的方式添加两个名称相同的bean

结论

以@Component的方式添加两个名称相同的bean,在 Spring 容器启动时会报错

验证过程

在类 DemoService01DemoService02 加上注解 @Component("demoService")

启动,报错如下:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘demoService’ for bean class [com.victor.demo.service.DemoService02] conflicts with existing, non-compatible bean definition of same name and class [com.victor.demo.service.DemoService01]

源码

原因是在扫描 @Component 注解时,会校验名称相同但是类型不同的 bean,会报错

我们先来到扫描 @Component 注解的核心方法,ClassPathBeanDefinitionScanner 类的 doScan 方法

image-20230925174834556

重点看这个 checkCandidate 方法

image-20230925174945279

这里先判断是否已经存在这个名称的 BeanDefinition,如果不存在,直接返回 true,如果存在,就调用 isCompatible 方法比对,这个方法如果返回是 false,就会走下面的 throw new ConflictingBeanDefinitionException 抛异常,我们看下 isCompatible 这个方法

image-20230925175148639

以下场景只要有一个满足,就会返回 true

  • 已注册的 BeanDefinition 不是通过扫描注册的
  • 新的这个 BeanDefinition 其实就是已注册的,只不过扫描了两遍

这里显然我们两个 BeanDefinition 都是通过扫描注册的,并且两个并不是同一个,所以就抛异常了

总结

1、在解析 xml 文件时,会去校验同一个文件中不能存在相同 ID 的 bean,如果存在,会抛异常

2、在解析 @Bean 注解时,会去校验同一个配置类中如果存在相同名称的 bean,如果存在,先注册的生效,后面的将不注册

3、在扫描 @Component 注解时,会校验名称相同的两个bean中,前一个如果是扫描注册的,且和后一个不是同一个bean,就会抛异常

4、前3个校验都是在真正注册 BeanDefinition 之前校验的,如果都通过了,会在真正进行注册的地方通过 allowBeanDefinitionOverriding 参数来判断是覆盖还是抛异常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1042049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基础设施建设-企业级全栈测试平台的最佳实践

QECon&#xff08;Quality Efficiency Conference&#xff09;质量效能大会在上海正式开幕&#xff01;本次大会以"数生智慧&#xff1a;高质量发展新引擎"为主题&#xff0c;深入探讨如何借助数字化和智能化技术推动软件质量的发展&#xff0c;为高质量经济发展提供…

华为云,让AI算力入山河

整个2023年&#xff0c;全球科技界都在为大模型沸腾。云计算产业作为AI大模型与产业场景间的最短路径&#xff0c;自然也在大模型浪潮中备受关注。目前阶段&#xff0c;云厂商已经纷纷入局大模型&#xff0c;从多个角度探索大模型带给云计算产业的可能性。 但我们往往会忽略这样…

【开发篇】八、SpringBoot整合MongoBD

文章目录 1、整合2、简单示例3、一点思考4、MongoDB的安装5、MongoDB的CRUD语法 1、整合 导入MongoBD的起步依赖&#xff1a;&#xff08;这个starter背后是MongoDB的驱动和其他依赖&#xff0c;在这儿也可以看出命名的规律&#xff0c;redis的就是spring-boot-starter-data-r…

【数据结构-图】并查集

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

接口测试主要测试哪方面?需要哪些技能?要怎么学习?

1、什么是接口测试&#xff1f; 定义&#xff1a;测试系统组件间接口的一种测试。主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点&#xff0c;重点是检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 目的&#x…

选择合适的外贸公司邮箱注册服务提供商

随着全球化的发展&#xff0c;越来越多的企业开始涉足外贸领域。而在进行外贸业务时&#xff0c;邮箱是必不可少的工具之一。对于外贸公司来说&#xff0c;应该选择哪家邮箱服务提供商呢&#xff1f;口碑好安全性高的公司邮箱有Zoho Mail、阿里邮箱、腾讯邮箱、网易邮箱等。 首…

Spring cloud Sentinel介绍和安装

Sentinel介绍和安装 &#x1f308;初识Sentinel&#x1f308;安装Sentinel&#x1f320;docker 安装&#x1f320;下载sentinel镜像&#x1f320;启动sentinel镜像 &#x1f320;windows 安装&#x1f320;下载&#x1f320;运行 &#x1f320;sentinel访问 &#x1f308;微服务…

上海股票开户佣金最低是多少?怎么开万一账户!

上海股票开户佣金最低是多少&#xff1f;怎么开万一账户&#xff01; 股票开户是指向证券公司或经纪人申请开通股票交易账户的过程。开户时需要提供个人身份信息和相关资料&#xff0c;完成相关的申请、签署协议以及缴纳开户费用。开户后&#xff0c;投资者就可以在证券市场上…

8年测试老鸟总结,Python自动化测试实现思路(细致)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试…

NVM:切换node版本后无法使用npm全局包

1.新建文件夹npm_global和npm_cache 2.npm设置 npm config set prefix "C:\Users\18068\node\node_global" npm config set cache "C:\Users\18068\node\node_cache" 3.设置环境变量 新建NVM_PATH环境变量 选择缓存目录 编辑PATH环境变量 新建%NVM_PA…

睿趣科技:新手抖音开店卖什么产品好

抖音已经成为了一款年轻人热爱的社交媒体应用&#xff0c;同时也成为了一种全新的电商平台。对于新手来说&#xff0c;抖音开店卖什么产品是一个备受关注的问题。在这篇文章中&#xff0c;我们将探讨一些适合新手的产品选择&#xff0c;帮助他们在抖音上开店获得成功。 流行时尚…

docker自定义网络下 :不同容器的nginx通过容器名称相互访问PHP项目

场景&#xff1a;每个服务都有自己运行的一套环境&#xff0c;分别都有自己的nginx &#xff1b;我们像用A容器的Nginx 的项目访问到B容器下的nginx项目内容&#xff0c;并且在自定义的网络下面 首先我们可以自定义一个网络&#xff1a;docker默认桥接&#xff1a; docker ne…

03. Springboot集成Mybatis-flex(一)

目录 1、前言 2、MyBatis-Flex 是什么&#xff1f; 3、框架功能对比 4、性能对比 5、快速使用 5.1、Maven添加依赖 5.2、数据源配置 5.3、创建实体类和表 5.4、创建Dao 5.5、创建Service 5.6、创建Controller接口测试 5.7、测试结果 6、小结 1、前言 现在主流的M…

git报错:git Permission denied, please try again.

1 问题描述: git clone/pull代码时提示输入密码,密码输入正确但是报错:git Permission denied, please try again. 2 解决方案: step1 检查SSH Key是否存在#cd ~/.ssh #lsstep2 获取SSH Key如果存在id_rsa.pub 或 id_dsa.pub 文件,跳过此步。 如果不存在,则生成: 先查…

浙江移动与中兴通讯合作项目被评为“光华杯”东部赛区一等奖!

近日&#xff0c;浙江移动携手中兴通讯共同打造的创新合作项目“构建面向东数西算的传输全光底座赋能美丽浙江”在2023年第二届“光华杯”东部赛区决赛中被评为一等奖&#xff0c;该项目以运力为核心&#xff0c;可实践应用于“东数西算”建设工程当中&#xff0c;提供高速无阻…

8. 基于消影点进行相机内参(主点)的标定

目录 1. ocam模型2. 消影点3. 基于消影点进行相机主点标定3.1 基于ocam模型的主点标定 感谢大家的阅读。 1. ocam模型 可以参考我的另一篇博客ocam模型。 这里简单提一下ocam模型&#xff1a; 这个模型将中心折反射相机和鱼眼相机统一在一个通用模型下&#xff0c;也称为泰勒模…

深眸科技迭代深度学习算法,以AI机器视觉技术扩围工业应用场景

智能制造是制造业数智化转型升级的发展方向&#xff0c;在当前以高端装备制造为核心的工业4.0时代背景下&#xff0c;越来越多的制造企业意识到机器视觉对于提高效率、降低成本&#xff0c;从而提升企业效益的意义。 目前&#xff0c;机器视觉已成为制造业迈向智能制造过程中极…

XC6206 低压线性稳压器 300mA低功耗LDO

XC6206系列是一款采用CMOS和激光修整技术制造的高精度、低能耗、3端子、正电压调压器。该系列提供了一个大的电流和一个明显的小的辍学电压。 XC6206由限流器电路、驱动器晶体管、精确参考电压和纠错电路组成。该系列兼容低ESR陶瓷电容。电流限制器的折叠电路作为短路保护以及输…

利用EXCEL进行XXE攻击

利用EXCEL进行XXE攻击 原因 原因 Microsoft Office从2007版本引入了新的开放的XML文件格式&#xff0c;新的XML文件格式基于压缩的ZIP文件格式规范&#xff0c;由许多部分组成。 我们可以将其解压缩到特定的文件夹中来查看其包含的文件夹和文件&#xff0c;可以发现其中多数是…

canvas绘制网格背景

/*** Event 方法* description: canvas 绘制网格背景* */drawGrid(element, lineColor, lineStepX, lineStepY, bgColor, bgStepX, bgStepY) {const canvas document.querySelector(element)const context canvas.getContext(2d)context.save();context.lineWidth 0.5;conte…