Spring 同名 Bean 加载策略

发布于: 2020 年 07 月 07 日

前言

在平时的开发中,你是否遇到过以下几种场景:

  • 不同的类声明了同一个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.1.0

我们用的启动类模板:

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;
/**
* @author xiaoxi666
* @date 2020-07-06 10:11
* 启动类模板程序,可根据需要添加不同的注解,以便引入对应的上下文。
*/
@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);
// 启动运行,并获取context
ApplicationContext context = application.run(args);
// 获取bean,并打印对应的实体类路径
Object object = context.getBean("myBean");
System.out.println(object.getClass().getName());
}
}

好了,让我们开始吧。

场景1

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

先定义bean:

package beans;
/**
* @author xiaoxi666
* @date 2020-07-06 10:31
*/
public interface X {
}

package beans;
import org.springframework.stereotype.Component;
/**
* @author xiaoxi666
* @date 2020-07-06 10:31
*/
@Component(value = "myBean")
public class XImpl1 implements X {
}

package beans;
import org.springframework.stereotype.Component;
/**
* @author xiaoxi666
* @date 2020-07-06 10:31
*/
@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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://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;
/**
* @author xiaoxi666
* @date 2020-07-06 10:11
* 启动类模板程序,可根据需要添加不同的注解,以便引入对应的上下文。
*/
@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:applicationContext1.xml", "classpath:applicationContext2.xml"})

换成

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

执行结果就会变成:

beans.XImpl1

场景2

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

同样,先定义bean:

package beans;
import org.springframework.stereotype.Component;
/**
* @author xiaoxi666
* @date 2020-07-06 10:45
*/
@Component(value = "myBean")
public class Y {
}

package beans;
import org.springframework.stereotype.Component;
/**
* @author xiaoxi666
* @date 2020-07-06 10:45
*/
@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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<bean id="myBean" class="beans.Z"/>
</beans>

执行结果与场景1类似。

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

场景3

场景描述:两个同名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;
/**
* @author xiaoxi666
* @date 2020-07-06 21:52
*/
public class MyAplicationInitializer implements ApplicationContextInitializer {
@Override
public 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的方式,可以参考文末列出的第一篇文章。

场景4

场景描述:两个同名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;
/**
* @author xiaoxi666
* @date 2020-07-06 11:02
*/
@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;
/**
* @author xiaoxi666
* @date 2020-07-06 10:11
*/
@Import(MyConfiguration.class)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {
public static void main(String[] args) {
...
}
}

执行结果:

beans.Y

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

package configuration;
import beans.Y;
import beans.Z;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author xiaoxi666
* @date 2020-07-06 11:02
*/
@Configuration
public class MyConfiguration {
@Bean(name = "myBean")
public Object z() {
return new Z();
}
@Bean(name = "myBean")
public Object y() {
return new Y();
}
}

执行结果就会变成:

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。

场景5

场景描述:两个同名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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://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;
/**
* @author xiaoxi666
* @date 2020-07-06 11:02
*/
@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;
/**
* @author xiaoxi666
* @date 2020-07-06 10:11
*/
@ImportResource({"classpath:applicationContext.xml"})
@Import(MyConfiguration.class)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Applicationloader {
public static void main(String[] args) {
...
}
}

执行结果:

beans.Y

如果把

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

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

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

原理和场景3相同,不再赘述。

场景6

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

与场景2类似,我们新增一个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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://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;
/**
* @author xiaoxi666
* @date 2020-07-06 10:11
* 启动类模板程序,可根据需要添加不同的注解,以便引入对应的上下文。
*/
@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 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

可以看到抛了异常,异常信息告诉我们:发现了两个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,直接报冲突。

场景7

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

与场景4类似,我们改一下配置文件:

package configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author xiaoxi666
* @date 2020-07-06 11:02
*/
@ComponentScan(basePackages = "beans")
@Configuration
public class MyConfiguration {
}

执行结果:

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,但它们不兼容。

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

场景8

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

我们通过xml扫描Y:

文件名: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/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://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;
/**
* @author xiaoxi666
* @date 2020-07-06 11:02
*/
@ComponentScan(basePackages = "beans",
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Z.class), useDefaultFilters = false)
@Configuration
public class MyConfiguration {
}

启动类:

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;
/**
* @author xiaoxi666
* @date 2020-07-06 10:11
*/
@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.BeanDefinitionParserDe