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


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

    结论

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

    验证过程

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

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    @SpringBootApplication
    @ImportResource("classpath:beans.xml")
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在 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  element", beanElement);
       }
    
       this.usedNames.add(beanName);
       this.usedNames.addAll(aliases);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

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

    结论

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

    验证过程

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

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在启动类上增加注解 @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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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
    
    • 1
    • 2
    • 3

    然后把启动类改下,获取下 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"));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    启动,控制台打印如下:

    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();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    通过 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    启动,控制台打印如下:

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    @Configuration
    public class DemoAutoConfiguration2 {
    
        @Bean("demoService")
        public DemoService02 demoService02() {
            return new DemoService02();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    启动,报错如下:

    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
    
    • 1
    • 2
    • 3

    启动,控制台打印如下:

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

  • 相关阅读:
    【Android】ViewRootImpl、WindowManagerGlobal和WindowManager之间的关系
    苍穹外卖-day11
    java微博 8 CSS
    【嵌入式模块】再探ESP8266,保姆级教程
    Springboot毕设项目车源后台管理系统f227y(java+VUE+Mybatis+Maven+Mysql)
    Vue2技能树(3)-声明式渲染、指令大全、生命周期函数
    Beego之Beego简介和安装
    Esbuild Bundler HMR
    第十五届蓝桥杯物联网试题(省赛)
    Telink泰凌微TLSR8258蓝牙开发笔记(二)
  • 原文地址:https://blog.csdn.net/dongdong199033/article/details/133294265