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

31 篇文章 1 订阅
2 篇文章 0 订阅

一、在同一个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 参数来判断是覆盖还是抛异常

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天进步亿点点的小码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值