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版本

我们用的启动类模板:


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

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

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


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

再定义 xml 配置文件:

文件名:applicationContext1.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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><bean id="myBean" class="beans.XImpl1"/>
</beans>

文件名:applicationContext2.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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><bean id="myBean" class="beans.XImpl2"/>
</beans>

 启动类

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

执行结果:

beans.XImpl2

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

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

执行结果就会变成:

beans.XImpl1

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

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

同样,先定义 bean:

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

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

文件名:applicationContext1.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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><bean id="myBean" class="beans.Y"/></beans>

文件名:applicationContext2.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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><bean id="myBean" class="beans.Z"/></beans>

执行结果与场景 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:

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {...BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}else if (existingDefinition.getRole() < beanDefinition.getRole()) {...}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 {...}this.beanDefinitionMap.put(beanName, beanDefinition);}else {...}if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}
}

可以看出,这里首先判断了 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:

package application;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;public class MyAplicationInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext context) {ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(false);}}
}

 然后注册自定义的 ApplicationInitializer:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {application.addInitializers(new MyAplicationInitializer);...}
}

这样就可以了。

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

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

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

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

package configuration;import beans.Y;
import beans.Z;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyConfiguration {@Bean(name = "myBean")public Object y() {return new Y();}@Bean(name = "myBean")public Object z() {return new Z();}
}
package application;import configuration.MyConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;@Import(MyConfiguration.class)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {...}
}

结果

beans.Y

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

执行结果就会变成:

beans.Z

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

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

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

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

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

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

我们通过 xml 声明 Y:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><bean id="myBean" class="beans.Y"/></beans>

通过 JavaConfig 声明 Z:

package configuration;import beans.Z;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyConfiguration {@Bean(name = "myBean")public Object z() {return new Z();}
}
package application;import configuration.MyConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;@ImportResource({"classpath:applicationContext.xml"})
@Import(MyConfiguration.class)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {...}
}
beans.Y

交换导入的位置

@Import(MyConfiguration.class)
@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 文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><context:component-scan base-package="beans"/></beans>

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

package application;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportResource;@ImportResource({"classpath:applicationContext.xml"})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {...}
}

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]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:419) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromImportedResources$0(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_171]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources(ConfigurationClassBeanDefinitionReader.java:325) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:144) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at application.Applicationloader.main(Applicationloader.java:23) [classes/:na]
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]at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:90) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at 

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

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {if (!this.registry.containsBeanDefinition(beanName)) {return true;}BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();if (originatingDef != null) {existingDef = originatingDef;}if (isCompatible(beanDefinition, existingDef)) {return false;}throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}

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

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

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

package configuration;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@ComponentScan(basePackages = "beans")
@Configuration
public class MyConfiguration {@Bean(name = "myBean")public Object y() {return new Y();}@Bean(name = "myBean")public Object z() {return new Z();}
}
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]at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:599) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:302) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:199) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:167) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:315) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at application.Applicationloader.main(Applicationloader.java:23) [classes/:na]
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]at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:287) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:589) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]... 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:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><context:component-scan base-package="beans" use-default-filters="false"><context:include-filter type="assignable" expression="beans.Y"/></context:component-scan></beans>

通过 JavaConfig 扫描 Z:

package configuration;import beans.Z;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;@ComponentScan(basePackages = "beans",includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Z.class), useDefaultFilters = false)
@Configuration
public class MyConfiguration {
}
@ImportResource({"classpath:applicationContext.xml"})
@Import(MyConfiguration.class)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {...}
}
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]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:419) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromImportedResources$0(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_171]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources(ConfigurationClassBeanDefinitionReader.java:325) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:144) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]at application.Applicationloader.main(Applicationloader.java:25) [classes/:na]
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]at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:90) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:74) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1366) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1352) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:179) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:149) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:96) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:513) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:393) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]... 21 common frames omitted

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

交换位置

@Import(MyConfiguration.class)
@ImportResource({"classpath:applicationContext.xml"})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {...}
}

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

因此我们可以得出结论:当 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:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd"><bean id="myBean" class="beans.Y"/><context:component-scan base-package="beans" use-default-filters="false"><context:include-filter type="assignable" expression="beans.Z"/></context:component-scan></beans>

@ImportResource({"classpath:applicationContext.xml"})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {public static void main(String[] args) {...}
}

结果

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,稍后解释):

/*** Check the given candidate's bean name, determining whether the corresponding* bean definition needs to be registered or conflicts with an existing definition.* @param beanName the suggested name for the bean* @param beanDefinition the corresponding bean definition* @return {@code true} if the bean can be registered as-is;* {@code false} if it should be skipped because there is an* existing, compatible bean definition for the specified name* @throws ConflictingBeanDefinitionException if an existing, incompatible* bean definition has been found for the specified name*/
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {if (!this.registry.containsBeanDefinition(beanName)) {return true;}BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();if (originatingDef != null) {existingDef = originatingDef;}if (isCompatible(beanDefinition, existingDef)) {return false;}throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}

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

/*** Determine whether the given new bean definition is compatible with* the given existing bean definition.* <p>The default implementation considers them as compatible when the existing* bean definition comes from the same source or from a non-scanning source.* @param newDefinition the new bean definition, originated from scanning* @param existingDefinition the existing bean definition, potentially an* explicitly defined one or a previously generated one from scanning* @return whether the definitions are considered as compatible, with the* new definition to be skipped in favor of the existing definition*/
protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {return (!(existingDefinition instanceof ScannedGenericBeanDefinition) ||  // explicitly registered overriding bean(newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) ||  // scanned same file twicenewDefinition.equals(existingDefinition));  // scanned equivalent class twice
}

我们知道,先前解析的 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:

/*** Parse the elements at the root level in the document:* "import", "alias", "bean".* @param root the DOM root element of the document*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}
}

注意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:

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

执行结果:

beans.Y

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

@ComponentScan(basePackages = "beans",includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Y.class), useDefaultFilters = false)
@Configuration
public class MyConfiguration {@Bean(name = "myBean")public Object z() {return new Z();}
}

执行结果就是:

beans.Z

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

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

上面的代码明确说明:通过 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 参数(提到了父子容器):

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【vSphere 8 自签名证书】企业 CA 签名证书替换 vSphere Machine SSL 证书Ⅳ—— 替换默认证书

目录 博文摘要6. 使用企业 CA 签发的 SSL 证书 替换 vSphere 默认 SSL 证书6.1 确认证书文件6.2 替换默认 vSphere 证书6.3 验证自签名证书6.4 补充说明 关联博文参考资料 博文摘要 博文主要描述了在 vCenter Server 8 上通过实用工具 certificate-manager 将 vSphere 默认 Ma…

差分时钟与DDR3

Zynq上的存储器接口 所有 Zynq-7000 AP芯片上的存储器接口单元包括一个动态存储器控制器和几个 静态存储器接口模块。动态存储器控制器可以用于 DDR3、DDR3L、DDR2 和 LPDDR2。 静态存储器控制器支持一个 NAND 闪存接口、一个 Quad-SPI 闪存接口、一个并行数 据总线和并行 NOR …

力扣刷题 day52:10-22

1.数组拆分 给定长度为 2n 的整数数组 nums &#xff0c;你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) &#xff0c;使得从 1 到 n 的 min(ai, bi) 总和最大。 返回该 最大总和 。 方法一&#xff1a;排序 #方法一&#xff1a;排序 def arrayPai…

uniapp开发微信小程序,webview内嵌h5,h5打开pdf地址,解决方案

根据公司要求&#xff0c;让我写一个h5&#xff0c;后续会嵌入到合作公司的微信小程序的webview中&#xff0c;如果是自己公司微信小程序&#xff0c;可以采取先下载下来pdf&#xff0c;然后通过wx.openDocument&#xff0c;进行单纯的预览操作&#xff0c;这个可以根据这个老哥…

工具让公众号推送变得轻而易举

公众号运营的关键在于定期向用户推送内容&#xff0c;但手动推送过程繁琐且浪费时间。现在&#xff0c;有了乔拓云公众号助手工具&#xff0c;你可以轻松实现公众号的自动推送功能。让我们一起来看看如何操作吧&#xff01; 首先&#xff0c;你需要注册一个乔拓云公众号助手工具…

【Rust】4 一文讲解重点 pattern matching | trait | 生命周期 | 闭包 | 迭代器 | 智能指针 | 并发与并行

文章目录 一、pattern matching二、trait2.1 常见 trait2.1.1 Copy 和 Clone2.1.2 PartialEq 和 Eq2.1.3 PartialOrd 和 Ord2.1.4 Hash2.1.5 From, Into, TryFrom, TryInto 2.2 概念2.2.1 关联类型2.2.2 关联常量2.3.3 泛型关联类型2.3.3.1 示例: 用泛型关联类型, 创建集合工厂…

【Docker从入门到入土 2】Docker数据管理、网络通信和网络模式 1.0

Part2 一、Docker网络模式&#xff08;面试高频&#xff09;1.1 Docker 网络实现原理1.2 host模式1.3 container模式1.4 none模式1.5 bridge模式1.6 自定义网络 二、Docker网络通信2.1 端口映射2.2 容器互联 三、Docker资源控制3.1 Cgroup简介3.2 CPU资源控制3.2.1 设置CPU使用…

Android C/C++ native编程NDK开发中logcat的使用

Android C/C native编程NDK开发中logcat的使用 前言具体用法 前言 在NDK开发过程中&#xff0c;C/C层&#xff0c;需要对代码进行一些调试&#xff0c;日志打印是我们解决异常或崩溃的重要手段&#xff0c;这里我就简单介绍下日志打印三步走。 首先我们先看下官方文档关于日志…

QSlider 类使用教程

文章目录 1、简介2 、公共类型3、属性4、functions4.1、访问属性相关 function4.2、公共槽4.3、Signal4.4、其他方法 5、设置样式 QT 官方文档参考地址&#xff1a;https://doc.qt.io/qt-5/qslider.html 1、简介 QSlider是垂直或水平滑块条控件&#xff0c;最常见的应用就是视…

DELM深度极限学习机回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

一秒开挂!工厂模式让你告别重复代码!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、工…

DC电源模块的数字电源优势

BOSHIDA DC电源模块的数字电源优势 数字电源模块是指在电源的设计和控制上采用数字式方案&#xff0c;采用数字化技术&#xff0c;将传统的电源模块从模拟传统电源转变为数字电源变成的模块。 传统的电源模块使用模拟技术&#xff0c;其主要优势在于可控性高、稳定性好&#…

运维人必须掌握的 5 种常用运维监控工具

运维人必须掌握的 5 种常用运维监控工具 运维监控工具千千万,仅开源的解决方案就有流量监控(MRTG、Cacti、SmokePing、Graphite 等)和性能告警(Nagios、Zabbix、Zenoss Core、Ganglia、OpenTSDB等)可供选择。 并且每种软件都有自己的特点和功能,各自的侧重点和目标不完全…

四大特性模块(module)

module的动机 C20中新增了四大特性之一的模块(module)&#xff0c;用以解决传统的头文件在编译时间及程序组织上的问题。 modules 试图解决的痛点 能最大的痛点就是编译慢, 头文件的重复替换, 比如你有多个翻译单元, 每一个都调用了 iostream, 就得都处理一遍. 预处理完的源…

如何开发一个 Safari 插件

本文字数&#xff1a;2493字 预计阅读时间&#xff1a;15分钟 由于常用浏览器是Safari&#xff0c;而Safari浏览器的插件比不上Chrome&#xff0c;所以就有了自己开发常用的Safari插件的想法。 打算开发当前页面生成二维码的Extension&#xff0c;因为网络原因&#xff0c;AirD…

DASCTF-CBCTF-2023 Crypto部分复现

文章目录 EzRSACB backpack 这次比赛没打&#xff0c;记错时间了&#xff0c;看了一下&#xff0c;如果去做的话大概也只能做出那两道简单的题&#xff0c;还是太菜啦 EzRSA 题目描述&#xff1a; from Crypto.Util.number import * import random from gmpy2 import * from …

屏幕截图软件Snagit 2023 mac中文特点介绍

Snagit 2023 mac是一款屏幕截图和视频录制软件&#xff0c;它可以帮助用户快速捕捉屏幕上的任何内容&#xff0c;并将其编辑、标注和共享。 Snagit 2023 软件特点 多种截图模式&#xff1a;支持全屏截图、窗口截图、区域截图、延时截图等多种截图模式&#xff0c;满足不同用户…

机器学习笔记 - 深度学习中跳跃连接的直观解释

一、概述 如今人们利用深度学习做无数的应用。然而,为了理解在许多作品中看到的大量设计选择(例如跳过连接),了解一点反向传播机制至关重要。 如果你在 2014 年尝试训练神经网络,你肯定会观察到所谓的梯度消失问题。简单来说:你在屏幕后面检查网络的训练过程,你看到的只…

STM32+2.9inch微雪墨水屏(电子纸)实现显示

本篇文章从硬件原理以及嵌入式编程等角度完整的介绍了墨水屏驱动过程&#xff0c;本例涉及的墨水屏为2.9inch e-Paper V2,它采用的是“微胶囊电泳显示”技术进行图像显示&#xff0c;其基本原理是悬浮在液体中的带电纳米粒子受到电场作用而产生迁移&#xff0c;从而改变显示屏各…

第63讲:MySQL存储过程变量传参的核心概念与案例

文章目录 1.存储过程传参2.存储过程传参的语法结构3.存储过程传参经典案例3.1.IN类型的传参3.2.IN和OUT同时使用的传参3.3.INOUT类型的传参 1.存储过程传参 在创建存储过程时&#xff0c;存储过程名称后面的括号里是可以传入一些参数的&#xff0c;例如传入一个变量&#xff0…