• Spring容器中同名 Bean 加载策略


    📢📢📢📣📣📣
    哈喽!大家好,我是「奇点」,江湖人称 singularity。刚工作几年,想和大家一同进步🤝🤝
    一位上进心十足的【Java ToB端大厂领域博主】!😜😜😜
    喜欢java和python,平时比较懒,能用程序解决的坚决不手动解决😜😜😜
    ✨ 如果有对【java】感兴趣的【小可爱】,欢迎关注我
    ❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️
    ————————————————
    如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。

    目录

    前言

    场景 1 两个同名 bean,对应的两个实体类分别是同一个接口的不同实现

    场景 2 两个同名 bean,对应的两个类完全没有关系

    总结 两个同名 bean,均通过 xml 的 bean 标签声明

    场景 3 两个同名 bean,均通过 JavaConfig 的 @Bean 注解声明

    场景 4 两个同名 bean,一个通过 xml 的 bean 标签声明,一个通过 JavaConfig 的 @Bean 注解声明。

    场景 5 两个同名 bean,均通过 xml 的 context:component-scan 标签扫描发现 bean。

    场景 6 两个同名 bean,均通过 Java Config 的注解 @ComponentScan 扫描发现 bean

    场景 7 两个同名 bean,一个通过 xml 的 context:component-scan 标签扫描发现,一个通过 Java Config 的注解 @ComponentScan 扫描发现

    场景 8 两个同名 bean,一个通过 xml 的 bean 标签声明,一个通过 xml 的 context:component-scan 标签扫描发现

    场景 9 两个同名 bean,一个通过 JavaConfig 的 @Bean 注解声明,一个通过 Java Config 的注解 @ComponentScan 扫描发现。

    场景梳理


    前言

    你是否遇到过以下问题:

    • 不同的类声明了同一个 bean 名字,有时这两个类实现了同一个接口,有时是完全无关的两个类。
    • 多个同名 bean,有的在 xml 中声明,有的以 Java Config 的方式声明。
    • xml 文件中,既配了 context:component-scan 标签扫描 bean,又通过 bean 标签声明了 bean,而且两种方式都可以取到同一个 bean。
    • Java Config 方式,既配了 @ComponentScan 注解扫描 bean,又通过注解 @Bean 声明 bean,而且两种方式都可以取到同一个 bean。
    • xml 和 Java Config 两种方式混合使用,两种方式都可以取到同一个 bean。

    那么问题来了,你清楚这几种场景下,Spring 会分别执行什么策略吗?也即:最终取到的 bean 到底是哪一个?

    既然有这么多种场景,那我们一一列举,看看到底是怎样执行的,背后的原理又是什么。


    开始前,先介绍一下环境:

    Spring Boot 2.x版本

    我们用的启动类模板

    1. package application;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    5. import org.springframework.context.ApplicationContext;
    6. import org.springframework.context.annotation.ImportResource;
    7. /**
    8. * 启动类模板程序,可根据需要添加不同的注解,以便引入对应的上下文。
    9. */
    10. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    11. public class Applicationloader {
    12. public static void main(String[] args) {
    13. SpringApplication application = new SpringApplication(Applicationloader.class);
    14. // Spring Boot版本>=2.1.0时,默认不允许bean覆盖。我们为了研究bean覆盖机制,将它改成允许覆盖。
    15. application.setAllowBeanDefinitionOverriding(true);
    16. // 启动运行,并获取context
    17. ApplicationContext context = application.run(args);
    18. // 获取bean,并打印对应的实体类路径
    19. Object object = context.getBean("myBean");
    20. System.out.println(object.getClass().getName());
    21. }
    22. }

    场景 1 两个同名 bean,对应的两个实体类分别是同一个接口的不同实现

    场景描述:两个同名 bean,对应的两个实体类分别是同一个接口的不同实现。

    1. package beans;
    2. public interface X {
    3. }
    1. package beans;
    2. import org.springframework.stereotype.Component;
    3. @Component(value = "myBean")
    4. public class XImpl1 implements X {
    5. }
    1. package beans;
    2. import org.springframework.stereotype.Component;
    3. @Component(value = "myBean")
    4. public class XImpl2 implements X {
    5. }

    再定义 xml 配置文件:

    文件名:applicationContext1.xml

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. "myBean" class="beans.XImpl1"/>

    文件名:applicationContext2.xml 

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. "myBean" class="beans.XImpl2"/>

     启动类

    1. package application;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    5. import org.springframework.context.ApplicationContext;
    6. import org.springframework.context.annotation.ImportResource;
    7. /**
    8. * 启动类模板程序,可根据需要添加不同的注解,以便引入对应的上下文。
    9. */
    10. @ImportResource({"classpath:applicationContext1.xml", "classpath:applicationContext2.xml"})
    11. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    12. public class Applicationloader {
    13. public static void main(String[] args) {
    14. ...
    15. }
    16. }

    执行结果:

    beans.XImpl2

    如果对调两个 xml 文件的顺序

    @ImportResource({"classpath:applicationContext2.xml", "classpath:applicationContext1.xml"})

    执行结果就会变成:

    beans.XImpl1

    场景 2 两个同名 bean,对应的两个类完全没有关系

    场景描述:两个同名 bean,对应的两个类完全没有关系。

    同样,先定义 bean:

    1. package beans;
    2. import org.springframework.stereotype.Component;
    3. @Component(value = "myBean")
    4. public class Y {
    5. }

    1. package beans;
    2. import org.springframework.stereotype.Component;
    3. @Component(value = "myBean")
    4. public class Z {
    5. }

    文件名:applicationContext1.xml 

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. "myBean" class="beans.Y"/>

    文件名:applicationContext2.xml

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. "myBean" class="beans.Z"/>

    执行结果与场景 1 类似。

    因此我们可以知道:同名 bean 的覆盖,与具体的类组织方式没有关系。

    总结 两个同名 bean,均通过 xml 的 bean 标签声明

    场景描述:两个同名 bean,均通过 xml 的 bean 标签声明。其实这就是上面的场景了。

    可以看出,最终使用的是后面的 xml 中声明的 bean。其实原因是“后面的 xml 中声明的 bean”把“前面的 xml 中声明的 bean”覆盖了。我们可以看到 Bebug 信息:

    Overriding bean definition for bean 'myBean' with a different definition: replacing [Generic bean: class [beans.Z]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [applicationContext2.xml]] with [Generic bean: class [beans.Y]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [applicationContext1.xml]]

    这段信息位于源码 org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition:

    1. @Override
    2. public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    3. throws BeanDefinitionStoreException {
    4. ...
    5. BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    6. if (existingDefinition != null) {
    7. if (!isAllowBeanDefinitionOverriding()) {
    8. throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
    9. }
    10. else if (existingDefinition.getRole() < beanDefinition.getRole()) {
    11. ...
    12. }
    13. else if (!beanDefinition.equals(existingDefinition)) {
    14. if (logger.isDebugEnabled()) {
    15. logger.debug("Overriding bean definition for bean '" + beanName +
    16. "' with a different definition: replacing [" + existingDefinition +
    17. "] with [" + beanDefinition + "]");
    18. }
    19. }
    20. else {
    21. ...
    22. }
    23. this.beanDefinitionMap.put(beanName, beanDefinition);
    24. }
    25. else {
    26. ...
    27. }
    28. if (existingDefinition != null || containsSingleton(beanName)) {
    29. resetBeanDefinition(beanName);
    30. }
    31. }

    可以看出,这里首先判断了 allowBeanDefinitionOverriding 属性,也即是否允许 bean 覆盖,如果允许的话,就继续判断 role、beanDefinition 等属性。当 debug 开启时,就会打印出上述的信息,告诉我们 bean 发生了覆盖行为。

    如果我们把 ApplicationLoader 中的这行代码删除:

    application.setAllowBeanDefinitionOverriding(true);

    由于 Spring Boot 2.1.0 及其以上版本默认不允许 bean 覆盖,此时会直接抛 BeanDefinitionOverrideException 异常,上面的源码也有体现。

    如果是在 Spring Boot 2.1.0 以下,默认是允许覆盖的,但 setAllowBeanDefinitionOverriding 方法也不存在(它是 2.1.0 加入的,具体可以参见官方文档)。

    那我们如果想设置该属性该怎么办呢?此时,我们可以参考 Spring Boot2.1.0 的实现 org.springframework.boot.SpringApplication#prepareContext。通过方法 addInitializers 给 SpringApplication 注册 ApplicationContextInitializer,并复写它的 initialize 方法,通过入参 ConfigurableApplicationContext 获取 DefaultListableBeanFactory,再调用 setAllowBeanDefinitionOverriding 进行设置。示例:

    首先,自定义 MyAplicationInitializer:

    1. package application;
    2. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    3. import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    4. import org.springframework.context.ApplicationContextInitializer;
    5. import org.springframework.context.ConfigurableApplicationContext;
    6. public class MyAplicationInitializer implements ApplicationContextInitializer {
    7. @Override
    8. public void initialize(ConfigurableApplicationContext context) {
    9. ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    10. if (beanFactory instanceof DefaultListableBeanFactory) {
    11. ((DefaultListableBeanFactory) beanFactory)
    12. .setAllowBeanDefinitionOverriding(false);
    13. }
    14. }
    15. }

     然后注册自定义的 ApplicationInitializer:

    1. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    2. public class Applicationloader {
    3. public static void main(String[] args) {
    4. application.addInitializers(new MyAplicationInitializer);
    5. ...
    6. }
    7. }

    这样就可以了。

    另外,网上还提到重定义 ContextLoader 的方式,可以参考文末列出的第一篇文章。

    场景 3 两个同名 bean,均通过 JavaConfig 的 @Bean 注解声明

    场景描述:两个同名 bean,均通过 JavaConfig 的 @Bean 注解声明。

    bean 的定义不变,我们增加一个配置类,替换之前的 xml 配置文件:

    1. package configuration;
    2. import beans.Y;
    3. import beans.Z;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. @Configuration
    7. public class MyConfiguration {
    8. @Bean(name = "myBean")
    9. public Object y() {
    10. return new Y();
    11. }
    12. @Bean(name = "myBean")
    13. public Object z() {
    14. return new Z();
    15. }
    16. }
    1. package application;
    2. import configuration.MyConfiguration;
    3. import org.springframework.boot.SpringApplication;
    4. import org.springframework.boot.autoconfigure.SpringBootApplication;
    5. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    6. import org.springframework.context.ApplicationContext;
    7. import org.springframework.context.annotation.Import;
    8. @Import(MyConfiguration.class)
    9. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    10. public class Applicationloader {
    11. public static void main(String[] args) {
    12. ...
    13. }
    14. }

    结果

    beans.Y

    如果把配置文件中 Y 和 Z 的顺序对调,也即:将其改成这样:

    执行结果就会变成:

    beans.Z

    可以看出,最终使用的是位置靠前的 bean。其实原因是“后面的 bean”被忽略了

    参考源码 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod: 

    1. private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    2. ...
    3. // Has this effectively been overridden before (e.g. via XML)?
    4. if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
    5. if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
    6. throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
    7. beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
    8. "' clashes with bean name for containing configuration class; please make those names unique!");
    9. }
    10. return;
    11. }
    12. ...
    13. }

    可知:如果发现后加载的 bean 可以被 overridden,就会将其忽略。因此最终使用的是先前被加载的 bean。

    场景 4 两个同名 bean,一个通过 xml 的 bean 标签声明,一个通过 JavaConfig 的 @Bean 注解声明。

    场景描述:两个同名 bean,一个通过 xml 的 bean 标签声明,一个通过 JavaConfig 的 @Bean 注解声明。

    我们通过 xml 声明 Y:

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. "myBean" class="beans.Y"/>

    通过 JavaConfig 声明 Z:

    1. package configuration;
    2. import beans.Z;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. @Configuration
    6. public class MyConfiguration {
    7. @Bean(name = "myBean")
    8. public Object z() {
    9. return new Z();
    10. }
    11. }
    1. package application;
    2. import configuration.MyConfiguration;
    3. import org.springframework.boot.SpringApplication;
    4. import org.springframework.boot.autoconfigure.SpringBootApplication;
    5. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    6. import org.springframework.context.ApplicationContext;
    7. import org.springframework.context.annotation.Import;
    8. import org.springframework.context.annotation.ImportResource;
    9. @ImportResource({"classpath:applicationContext.xml"})
    10. @Import(MyConfiguration.class)
    11. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    12. public class Applicationloader {
    13. public static void main(String[] args) {
    14. ...
    15. }
    16. }
    beans.Y

    交换导入的位置

    1. @Import(MyConfiguration.class)
    2. @ImportResource({"classpath:applicationContext.xml"})

    两者的上下位置对调一下,输出结果也不变。

    因此可以得出结论:当 xml 和 Java Config 均采用注解引入时,最终拿到的 bean 是 xml 文件中声明的。原因是 xml 在 Java Config 之后加载,把 Java Config 声明的 bean 覆盖了。此时我们可以看到 Debug 信息:

    Overriding bean definition for bean 'myBean' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=configuration.MyConfiguration; factoryMethodName=z; initMethodName=null; destroyMethodName=(inferred); defined in configuration.MyConfiguration] with [Generic bean: class [beans.Y]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [applicationContext.xml]]

    场景 5 两个同名 bean,均通过 xml 的 context:component-scan 标签扫描发现 bean。

    场景描述:两个同名 bean,均通过 xml 的 context:component-scan 标签扫描发现 bean。

    applicationContext.xml 文件:

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. package="beans"/>

    由于采用了扫描的方式,我们不用写两个 xml 文件分别声明两个 bean 了,现在一个 applicationContext.xml 文件就可以搞定。

    1. package application;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    5. import org.springframework.context.ApplicationContext;
    6. import org.springframework.context.annotation.ImportResource;
    7. @ImportResource({"classpath:applicationContext.xml"})
    8. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    9. public class Applicationloader {
    10. public static void main(String[] args) {
    11. ...
    12. }
    13. }

    1. org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext.xml]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myBean' for bean class [beans.Z] conflicts with existing, non-compatible bean definition of same name and class [beans.Y]
    2. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:419) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    3. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    4. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    5. at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    6. at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    7. at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    8. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromImportedResources$0(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    9. at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_171]
    10. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources(ConfigurationClassBeanDefinitionReader.java:325) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    11. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:144) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    12. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    13. at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    14. at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    15. at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    16. at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    17. at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    18. at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    19. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    20. at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    21. at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    22. at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    23. at application.Applicationloader.main(Applicationloader.java:23) [classes/:na]
    24. Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myBean' for bean class [beans.Z] conflicts with existing, non-compatible bean definition of same name and class [beans.Y]
    25. at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    26. at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    27. at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:90) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    28. at

    可以看到抛了异常,异常信息告诉我们:发现了两个 bean,但它们不兼容。抛异常的源码位于 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate:

    1. protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
    2. if (!this.registry.containsBeanDefinition(beanName)) {
    3. return true;
    4. }
    5. BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
    6. BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
    7. if (originatingDef != null) {
    8. existingDef = originatingDef;
    9. }
    10. if (isCompatible(beanDefinition, existingDef)) {
    11. return false;
    12. }
    13. throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
    14. "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
    15. "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
    16. }

    这段代码执行时机很早(要知道我们现在是允许同名 bean 覆盖的,但显然可以看出,还没有走到判断 allowBeanDefinitionOverriding 属性的地方),扫描出来就检查候选 bean,发现有两个同名 bean,直接报冲突。

    场景 6 两个同名 bean,均通过 Java Config 的注解 @ComponentScan 扫描发现 bean

    场景描述:两个同名 bean,均通过 Java Config 的注解 @ComponentScan 扫描发现 bean。

    1. package configuration;
    2. import org.springframework.context.annotation.ComponentScan;
    3. import org.springframework.context.annotation.Configuration;
    4. @ComponentScan(basePackages = "beans")
    5. @Configuration
    6. public class MyConfiguration {
    7. @Bean(name = "myBean")
    8. public Object y() {
    9. return new Y();
    10. }
    11. @Bean(name = "myBean")
    12. public Object z() {
    13. return new Z();
    14. }
    15. }
    1. org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [application.Applicationloader]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myBean' for bean class [beans.Z] conflicts with existing, non-compatible bean definition of same name and class [beans.Y]
    2. at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:599) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    3. at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:302) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    4. at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    5. at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:199) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    6. at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:167) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    7. at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:315) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    8. at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    9. at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    10. at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    11. at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    12. at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    13. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    14. at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    15. at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    16. at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    17. at application.Applicationloader.main(Applicationloader.java:23) [classes/:na]
    18. Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myBean' for bean class [beans.Z] conflicts with existing, non-compatible bean definition of same name and class [beans.Y]
    19. at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    20. at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    21. at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    22. at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:287) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    23. at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    24. at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:589) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    25. ... 15 common frames omitted

    可以看到抛了异常,异常信息告诉我们:发现了两个 bean,但它们不兼容。

    同时,我们可以看到,场景 5和 6 类似,抛的异常相同。但由于场景 5 是 xml 解析,场景 6 是 Java Config 解析,因此具体的堆栈信息有些差异。

    场景 7 两个同名 bean,一个通过 xml 的 context:component-scan 标签扫描发现,一个通过 Java Config 的注解 @ComponentScan 扫描发现

    场景描述:两个同名 bean,一个通过 xml 的 context:component-scan 标签扫描发现,一个通过 Java Config 的注解 @ComponentScan 扫描发现。

    文件名:applicationContext.xml  通过 xml 扫描 Y:

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. package="beans" use-default-filters="false">
    10. "assignable" expression="beans.Y"/>

    通过 JavaConfig 扫描 Z:

    1. package configuration;
    2. import beans.Z;
    3. import org.springframework.context.annotation.ComponentScan;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.context.annotation.FilterType;
    6. @ComponentScan(basePackages = "beans",
    7. includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Z.class), useDefaultFilters = false)
    8. @Configuration
    9. public class MyConfiguration {
    10. }
    1. @ImportResource({"classpath:applicationContext.xml"})
    2. @Import(MyConfiguration.class)
    3. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    4. public class Applicationloader {
    5. public static void main(String[] args) {
    6. ...
    7. }
    8. }
    1. org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext.xml]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myBean' for bean class [beans.Y] conflicts with existing, non-compatible bean definition of same name and class [beans.Z]
    2. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:419) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    3. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    4. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    5. at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    6. at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    7. at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    8. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromImportedResources$0(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    9. at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_171]
    10. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources(ConfigurationClassBeanDefinitionReader.java:325) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    11. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:144) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    12. at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    13. at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    14. at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    15. at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    16. at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    17. at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    18. at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    19. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    20. at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    21. at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    22. at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    23. at application.Applicationloader.main(Applicationloader.java:25) [classes/:na]
    24. Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myBean' for bean class [beans.Y] conflicts with existing, non-compatible bean definition of same name and class [beans.Z]
    25. at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    26. at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    27. at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:90) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    28. at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:74) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    29. at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1366) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    30. at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1352) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    31. at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:179) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    32. at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:149) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    33. at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:96) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    34. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:513) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    35. at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:393) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    36. ... 21 common frames omitted

    发现抛异常,异常信息和场景 5 一致,都是在 xml 解析过程中抛的异常。

    交换位置

    1. @Import(MyConfiguration.class)
    2. @ImportResource({"classpath:applicationContext.xml"})
    3. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    4. public class Applicationloader {
    5. public static void main(String[] args) {
    6. ...
    7. }
    8. }

    再次执行。发现和上面抛的异常一致。

    因此我们可以得出结论:当 xml 和 Java Config 都扫描 bean 时,注解 @ComponentScan 会先于 xml 标签中的 context:component-scan 标签执行(因为抛异常的点在解析后者的过程中,也可以调试源码得出相同的结论,参见下图)。

    场景 8 两个同名 bean,一个通过 xml 的 bean 标签声明,一个通过 xml 的 context:component-scan 标签扫描发现

    场景描述:两个同名 bean,一个通过 xml 的 bean 标签声明,一个通过 xml 的 context:component-scan 标签扫描发现。

    我们通过 xml 的 bean 标签声明 Y,并通过 xml 的 context:component-scan 标签扫描发现 Z:

    1. "1.0" encoding="UTF-8"?>
    2. "http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    9. "myBean" class="beans.Y"/>
    10. package="beans" use-default-filters="false">
    11. "assignable" expression="beans.Z"/>

    1. @ImportResource({"classpath:applicationContext.xml"})
    2. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    3. public class Applicationloader {
    4. public static void main(String[] args) {
    5. ...
    6. }
    7. }

    结果

    beans.Y

    如果我们通过 xml 的 bean 标签声明 Z,并通过 xml 的 context:component-scan 标签扫描发现 Y 的话,执行结果就会是:

    beans.Z

    可以看出,最终使用的是通过 xml 的 bean 标签声明的 bean,而非通过 xml 的 context:component-scan 标签扫描发现的 bean。

    我们跟踪源码会发现,在注册 bean 前,会在 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate 方法中,判断两个 bean 是否兼容(第 21 行代码),如果兼容的话会返回 false,bean 就不会被注册了(注意:这里的解析顺序是先解析通过 xml 的 bean 标签声明的 bean,后解析通过 xml 的 context:component-scan 标签扫描发现的 bean,稍后解释):

    1. /**
    2. * Check the given candidate's bean name, determining whether the corresponding
    3. * bean definition needs to be registered or conflicts with an existing definition.
    4. * @param beanName the suggested name for the bean
    5. * @param beanDefinition the corresponding bean definition
    6. * @return {@code true} if the bean can be registered as-is;
    7. * {@code false} if it should be skipped because there is an
    8. * existing, compatible bean definition for the specified name
    9. * @throws ConflictingBeanDefinitionException if an existing, incompatible
    10. * bean definition has been found for the specified name
    11. */
    12. protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
    13. if (!this.registry.containsBeanDefinition(beanName)) {
    14. return true;
    15. }
    16. BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
    17. BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
    18. if (originatingDef != null) {
    19. existingDef = originatingDef;
    20. }
    21. if (isCompatible(beanDefinition, existingDef)) {
    22. return false;
    23. }
    24. throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
    25. "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
    26. "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
    27. }

    具体地:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#isCompatible

    1. /**
    2. * Determine whether the given new bean definition is compatible with
    3. * the given existing bean definition.
    4. *

      The default implementation considers them as compatible when the existing

    5. * bean definition comes from the same source or from a non-scanning source.
    6. * @param newDefinition the new bean definition, originated from scanning
    7. * @param existingDefinition the existing bean definition, potentially an
    8. * explicitly defined one or a previously generated one from scanning
    9. * @return whether the definitions are considered as compatible, with the
    10. * new definition to be skipped in favor of the existing definition
    11. */
    12. protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
    13. return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
    14. (newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) || // scanned same file twice
    15. newDefinition.equals(existingDefinition)); // scanned equivalent class twice
    16. }

    我们知道,先前解析的 bean 是通过 xml 的 bean 标签声明的,因此 existingDefinition 的类型是 org.springframework.beans.factory.support.GenericBeanDefinition,因此,条件

    !(existingDefinition instanceof ScannedGenericBeanDefinition)

    为 true,也就表示兼容,因此该方法返回 true。附注:回顾一下场景 5、6、7,它们就是在方法 checkCandidate 中抛了异常,因为这 3 个场景中的两个 bean 都是扫描发现的,因此 existingDefinition 的类型是 ScannedGenericBeanDefinition,会被判定为不兼容。

    最终会导致通过 xml 的 context:component-scan 标签扫描发现的 bean 未被注册。因此我们最终使用的是通过 xml 的 bean 标签声明的 bean。

    前面留了个小尾巴:解析顺序是先解析通过 xml 的 bean 标签声明的 bean,后解析通过 xml 的 context:component-scan 标签扫描发现的 bean。源码位于 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions:

    1. /**
    2. * Parse the elements at the root level in the document:
    3. * "import", "alias", "bean".
    4. * @param root the DOM root element of the document
    5. */
    6. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    7. if (delegate.isDefaultNamespace(root)) {
    8. NodeList nl = root.getChildNodes();
    9. for (int i = 0; i < nl.getLength(); i++) {
    10. Node node = nl.item(i);
    11. if (node instanceof Element) {
    12. Element ele = (Element) node;
    13. if (delegate.isDefaultNamespace(ele)) {
    14. parseDefaultElement(ele, delegate);
    15. }
    16. else {
    17. delegate.parseCustomElement(ele);
    18. }
    19. }
    20. }
    21. }
    22. else {
    23. delegate.parseCustomElement(root);
    24. }
    25. }

    注意parseDefaultElement(ele, delegate);和delegate.parseCustomElement(ele);

    parseDefaultElement用于解析默认命名空间的标签,

    delegate.parseCustomElement用于解析自定义命名空间的标签。

    bean 标签属于默认命名空间,而 component-scan 属于自定义的命名空间。明显可以看出:先解析通过 xml 的 bean 标签声明的 bean,后解析通过 xml 的 context:component-scan 标签扫描发现的 bean。

    场景 9 两个同名 bean,一个通过 JavaConfig 的 @Bean 注解声明,一个通过 Java Config 的注解 @ComponentScan 扫描发现。

    场景描述:两个同名 bean,一个通过 JavaConfig 的 @Bean 注解声明,一个通过 Java Config 的注解 @ComponentScan 扫描发现。

    我们通过 JavaConfig 的 @Bean 注解声明 Y,并通过 Java Config 的注解 @ComponentScan 扫描发现 Z:

    1. @ComponentScan(basePackages = "beans",
    2. includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Z.class), useDefaultFilters = false)
    3. @Configuration
    4. public class MyConfiguration {
    5. @Bean(name = "myBean")
    6. public Object y() {
    7. return new Y();
    8. }
    9. }
    1. @Import(MyConfiguration.class)
    2. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    3. public class Applicationloader {
    4. public static void main(String[] args) {
    5. ...
    6. }
    7. }

    执行结果:

    beans.Y

    如果把 Y 和 Z 的声明方式对调一下,也即配置文件改成:

    1. @ComponentScan(basePackages = "beans",
    2. includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Y.class), useDefaultFilters = false)
    3. @Configuration
    4. public class MyConfiguration {
    5. @Bean(name = "myBean")
    6. public Object z() {
    7. return new Z();
    8. }
    9. }

    执行结果就是:

    beans.Z

     可以看出,最终使用的是通过注解 @Bean 声明的 bean。通过源码可以看出,“通过注解 @ComponentScan 扫描的 bean”被“通过注解 @Bean 声明的 bean”覆盖了,源码位于 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#isOverriddenByExistingDefinition:

    1. protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
    2. ...
    3. // A bean definition resulting from a component scan can be silently overridden
    4. // by an @Bean method, as of 4.2...
    5. if (existingBeanDef instanceof ScannedGenericBeanDefinition) {
    6. return false;
    7. }
    8. ...
    9. }

    上面的代码明确说明:通过 component scan 扫描的 bean 会被通过 @Bean 声明的 bean 覆盖掉,而且这种覆盖没有任何提示,也即 silently(悄悄地)覆盖掉。

    场景梳理

    根据不同的维度,我们梳理一下上面的场景,方便对号入座:

    1. 根据实体类区分(这两种情况下的覆盖策略是相同的)

    • 同一个接口的两个实现,对应的的两个 bean 同名。(场景 1

    • 两个同名 bean,对应的两个类完全没有关系(场景 2

    1. 根据配置方式区分

    • xml 方式(场景 3

    • Java config 方式(场景 3

    • xml 和 Java config 方式混用(场景 4:最终使用的是 xml 配置的 bean)

    1. 根据 bean 发现方式区分(通过 @Bean 注解声明的 bean,会将 @ComponentScan 扫描的 bean 覆盖)

    • xml 的 component-scan 扫描方式(场景 5

    • Java Config 的 @ComponentScan 扫描方式(场景 6

    • xml 的 component-scan 扫描方式 和 Java Config 的 @ComponentScan 扫描方式 混用(场景 7

    • xml 的 bean 标签方式(场景 3

    • Java Config 的 @Bean 注解声明 bean(场景 3

    • xml 的 component-scan 扫描方式 和 bean 标签方式混用(场景 8

    • Java Config 的 @ComponentScan 扫描方式 和 通过 @Bean 注解声明 bean 混用(场景 9

    • 本文列举了平时开发中可能遇到的多种 bean 配置方式,并且简析了相关源码,解释了执行结果。

    • 本文并未讲解 bean 解析整体流程,因此强烈建议读者手动调试,自己过一遍源码。

    • 很多细节问题在方法源码注释标注了,这些内容在 Spring 的官方文档也有说明。建议抽空看一下官方文档,也许很多问题就迎刃而解了。

    • 资源

    • Github:https://github.com/xiaoxi666/spring-demo/tree/bean_name,该分支搭建好了 SpringBoot 环境,并配置了 logback 日志。。

    • 重定义 ContextLoader,控制 isAllowBeanDefinitionOverridng 参数(提到了父子容器):

  • 相关阅读:
    JTabbedPane 点击+新建选项卡
    IC验证| Verilog语法详解之条件语句
    jwt对token的生成以及验证机制
    ES (ElasticSearch) 简易解读(四)Docker环境下安装和配置;非常简单的方式
    Java OpenJDK 8u392 Windows x64
    Azure DevOps(三)Azure Pipeline 自动化将程序包上传到 Azure Blob Storage
    HTML,CSS,JavaScript知识点
    流媒体传输 - HLS 协议
    Kubernetes - Kubernetes部署“容器化应用”(二)
    做自媒体怎样在一年之内赚到 10万元?
  • 原文地址:https://blog.csdn.net/qq_29235677/article/details/129108744