文章目录
- 一、在同一个xml配置文件里配置两个相同ID的bean
- 结论
- 验证过程
- 源码
- 二、在不同xml配置文件里配置两个相同ID的bean
- 结论
- 验证过程
- 源码
- 三、在同一个配置类中以@Bean方式添加两个名称相同的bean
- 结论
- 验证过程
- 源码
- 四、在不同配置类中以@Bean方式添加两个名称相同的bean
- 结论
- 验证过程
- 源码
- 五、以@Component的方式添加两个名称相同的bean
- 结论
- 验证过程
- 源码
- 总结
一、在同一个xml配置文件里配置两个相同ID的bean
结论
如果在同一个 xml 配置文件里配置两个相同 ID 的 bean,在 Spring 容器启动时会报错
验证过程
在 resources
目录下增加 beans.xml
文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="demoService" class="com.victor.demo.service.DemoService01"/>
<bean id="demoService" class="com.victor.demo.service.DemoService02"/>
</beans>
在启动类上增加注解 @ImportResource("classpath:beans.xml")
@SpringBootApplication
@ImportResource("classpath:beans.xml")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
在 service 目录下添加两个类,DemoService01
,DemoService02
,就不贴代码了,空类就可以
然后启动,会报如下错
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean name ‘demoService’ is already used in this element
Offending resource: class path resource [beans.xml]
源码
原因是,Spring 在解析 xml
文件的时候,会校验 bean 的 ID 唯一性,具体代码在 BeanDefinitionParserDelegate
的 checkNameUniqueness
方法里
//BeanDefinitionParserDelegate
/**
* Validate that the specified bean name and aliases have not been used already
* within the current level of beans element nesting.
*/
protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
String foundName = null;
if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
foundName = beanName;
}
if (foundName == null) {
foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
}
if (foundName != null) {
error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
}
this.usedNames.add(beanName);
this.usedNames.addAll(aliases);
}
这里会维护一个叫 usedNames
的 HashSet
集合,记录已经使用的 beanName
,也就是 ID
二、在不同xml配置文件里配置两个相同ID的bean
结论
如果在不同 xml 配置文件里配置两个相同 ID 的 bean,在 Spring 容器启动时会报错,但是增加 allow-bean-definition-overriding=true 参数后,后加载的会覆盖先加载的
验证过程
在 resources
目录下增加 beans.xml
和 beans2.xml
文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="demoService" class="com.victor.demo.service.DemoService01"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="demoService" class="com.victor.demo.service.DemoService02"/>
</beans>
在启动类上增加注解 @ImportResource({"classpath:beans.xml", "classpath:beans2.xml"})
@SpringBootApplication
@ImportResource({"classpath:beans.xml", "classpath:beans2.xml"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
DemoService01
,DemoService02
两个类保持不变
启动,报错如下:
Description:
The bean ‘demoService’, defined in class path resource [beans2.xml], could not be registered. A bean with that name has already been defined in class path resource [beans.xml] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
其实报错信息里提示我们了,把参数 spring.main.allow-bean-definition-overriding
设置成 true
,好,我们去设置一下,在 application.yml
文件里添加
spring:
main:
allow-bean-definition-overriding: true
然后把启动类改下,获取下 demoService,打印一下
@SpringBootApplication
@ImportResource({"classpath:beans.xml", "classpath:beans2.xml"})
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
System.out.println(applicationContext.getBean("demoService"));
}
}
启动,控制台打印如下:
com.victor.demo.service.DemoService02@5dfe23e8
可以看到确实是覆盖了
源码
原因是 Spring 在注册 BeanDefinition
的时候,会比对已经注册的 BeanDefinition
,然后通过参数 allowBeanDefinitionOverriding
判断是否覆盖,如果配置为 true,就覆盖,如果是 false,就抛异常,具体代码在 DefaultListableBeanFactory
的 registerBeanDefinition
方法里
//DefaultListableBeanFactory
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
//从已经注册的 beanDefinition 中获取,如果获取到,就说明已经存在
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
//判断 allowBeanDefinitionOverriding 参数是否是 true
if (!isAllowBeanDefinitionOverriding()) {
//如果是 false,就抛出 BeanDefinitionOverrideException 异常
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
通过 BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
从已经注册的 beanDefinition
中获取,如果获取到,就说明已经存在,然后判断 allowBeanDefinitionOverriding
参数是否是 true,如果是 false,就抛出 BeanDefinitionOverrideException
异常
三、在同一个配置类中以@Bean方式添加两个名称相同的bean
结论
如果在同一个配置类中以@Bean方式添加两个名称相同的bean,会注册第一个bean
验证过程
添加配置类 DemoAutoConfiguration
@Configuration
public class DemoAutoConfiguration {
@Bean("demoService")
public DemoService01 demoService01() {
return new DemoService01();
}
@Bean("demoService")
public DemoService02 demoService02() {
return new DemoService02();
}
}
启动,控制台打印如下:
com.victor.demo.service.DemoService01@682abca7
可以看到确实注册了第一个
源码
关于 @Configuration 注解和 @Bean 注解解析的源码在 ConfigurationClassPostProcessor
这个BeanDefinitionRegistryPostProcessor
里(关于 BeanDefinitionRegistryPostProcessor
这里就不讲了),重点在 processConfigBeanDefinitions
方法里
下面这一步具体会在 ConfigurationClassBeanDefinitionReader
类的 loadBeanDefinitionsForConfigurationClass
方法里
然后看看这个 loadBeanDefinitionsForBeanMethod
方法
这里有一个 isOverriddenByExistingDefinition
方法,判断是否被已存在的 BeanDefinition
覆盖,如果返回 true,就被已存在的 BeanDefinition
覆盖,也就是当前这个不注册,直接return,具体看看 isOverriddenByExistingDefinition
方法
先通过 if (existingBeanDef instanceof ConfigurationClassBeanDefinition)
看 bean 是不是配置类创建的,如果是就通过 if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName()))
判断配置类是不是同一个,如果是同一个配置类,就被已存在的覆盖,也就是只注册第一个,如果配置类不是同一个呢,这里是会通过了,但后面呢?再来试试
四、在不同配置类中以@Bean方式添加两个名称相同的bean
结论
如果在不同配置类中以@Bean方式添加两个名称相同的bean,在 Spring 容器启动时会报错,但是增加 allow-bean-definition-overriding=true 参数后,后加载的会覆盖先加载的
验证过程
添加配置类 DemoAutoConfiguration
、DemoAutoConfiguration2
@Configuration
public class DemoAutoConfiguration {
@Bean("demoService")
public DemoService01 demoService01() {
return new DemoService01();
}
}
@Configuration
public class DemoAutoConfiguration2 {
@Bean("demoService")
public DemoService02 demoService02() {
return new DemoService02();
}
}
启动,报错如下:
Description:
The bean ‘demoService’, defined in class path resource [com/victor/demo/config/DemoAutoConfiguration2.class], could not be registered. A bean with that name has already been defined in class path resource [com/victor/demo/config/DemoAutoConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
和 **二、在不同 xml 配置文件里配置两个相同ID的bean **里差不多的提示,我们在 application.yml
文件里添加配置
spring:
main:
allow-bean-definition-overriding: true
启动,控制台打印如下:
com.victor.demo.service.DemoService02@2539cd1c
可以看到确实是覆盖了
源码
其实这里我们不看源码也可以知道了,虽然在 三、在同一个配置类中以@Bean方式添加两个名称相同的bean 源码分析里,那边没有return,但是到注册 BeanDefinition
的时候,还是会校验,就像 **二、在不同 xml 配置文件里配置两个相同ID的bean **那样,也可以理解,本身 Spring 推出 @Configuration + @Bean
的方式就是为了取代 xml
配置的方式,不同的配置类就像是不同的 xml
配置文件一样
五、以@Component的方式添加两个名称相同的bean
结论
以@Component的方式添加两个名称相同的bean,在 Spring 容器启动时会报错
验证过程
在类 DemoService01
和 DemoService02
加上注解 @Component("demoService")
启动,报错如下:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘demoService’ for bean class [com.victor.demo.service.DemoService02] conflicts with existing, non-compatible bean definition of same name and class [com.victor.demo.service.DemoService01]
源码
原因是在扫描 @Component
注解时,会校验名称相同但是类型不同的 bean,会报错
我们先来到扫描 @Component
注解的核心方法,ClassPathBeanDefinitionScanner 类的 doScan 方法
重点看这个 checkCandidate
方法
这里先判断是否已经存在这个名称的 BeanDefinition
,如果不存在,直接返回 true,如果存在,就调用 isCompatible
方法比对,这个方法如果返回是 false,就会走下面的 throw new ConflictingBeanDefinitionException
抛异常,我们看下 isCompatible
这个方法
以下场景只要有一个满足,就会返回 true
- 已注册的
BeanDefinition
不是通过扫描注册的 - 新的这个
BeanDefinition
其实就是已注册的,只不过扫描了两遍
这里显然我们两个 BeanDefinition
都是通过扫描注册的,并且两个并不是同一个,所以就抛异常了
总结
1、在解析 xml
文件时,会去校验同一个文件中不能存在相同 ID 的 bean,如果存在,会抛异常
2、在解析 @Bean
注解时,会去校验同一个配置类中如果存在相同名称的 bean,如果存在,先注册的生效,后面的将不注册
3、在扫描 @Component
注解时,会校验名称相同的两个bean中,前一个如果是扫描注册的,且和后一个不是同一个bean,就会抛异常
4、前3个校验都是在真正注册 BeanDefinition
之前校验的,如果都通过了,会在真正进行注册的地方通过 allowBeanDefinitionOverriding
参数来判断是覆盖还是抛异常