Spring中配置id或name相同的Bean可能引发的问题及解决方案

一、背景

如果再xml中配置了相同的的ID或name可能会造成一些问题,今天我们来探讨一下并解决。

二、问题

1、在同一个xml中配置了相同的bean的id。EX:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "/spring-beans.dtd">
<beans>
  <bean id="test" class="com.xxx.Bean">
    <property name="name" value="111" />
  </bean>
</beans> <beans>
  <bean id="test" class="com.xxx.Bean">
    <property name="name" value="222" />
  </bean>
</beans>
复制代码

这种情况下,会直接抛出异常"Cannot register bean definition [xxx] for bean xxx: There is already [xxx] bound."

2、在不同的xml中配置相同的bean的id。EX:

test1.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "/spring-beans.dtd">
<beans>
  <bean id="test" class="com.xxx.Bean">
    <property name="name" value="111" />
  </bean>
</beans>
复制代码

test2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "/spring-beans.dtd">
<beans>
  <bean id="test" class="com.xxx.Bean">
    <property name="name" value="222" />
  </bean>
</beans>
复制代码

这种情况下text2.xml中的bean会直接覆盖text1.xml中的bean,Spring最终只会把text2.xml中的bean加载到IOC容器中。

此时spring并不会报错,只会打印info级别的日志信息,"Overriding bean definition for bean xxx with a different definition: replacing [xxx] with [xxx]" 这种情况下,要排查问题很困难。

三、解决

通过查看spring源码,我们发现在springIOC容器初始化时,有一个关键变量allowBeanDefinitionOverriding。

再来看下源码:

@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		// 校验 beanName 与 beanDefinition 非空
		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		//校验解析的BeanDefiniton
		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		// 从缓存中获取指定 beanName 的 BeanDefinition
		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		// 如果已经存在
		if (oldBeanDefinition != null) {
			// 如果存在但是不允许覆盖,抛出异常
			if (!isAllowBeanDefinitionOverriding()) {------------------6
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			// 覆盖 beanDefinition 大于 被覆盖的 beanDefinition 的 ROLE ,打印 info 日志
			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							oldBeanDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(oldBeanDefinition)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			// 允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
复制代码

可以看到在第6行,通过判断allowBeanDefinitionOverriding变量的值,来决定是覆盖还是抛出异常,而allowBeanDefinitionOverriding这个值默认是true。所以默认情况下是直接覆盖的,不会抛出异常。

那么我们很容易就想到,把allowBeanDefinitionOverriding的值改为false就可以解决问题。

查看源码,我们发现DefaultListableBeanFactory类提供了赋值allowBeanDefinitionOverriding变量的方法:

public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { 
   this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; 
}
复制代码

所以我们只要调用这个方法,把allowBeanDefinitionOverriding赋值成false就成功了。

那么如何修改呢? 我们来看下都有哪些类调用了setAllowBeanDefinitionOverriding()方法:

可以看到,在AbstractRefreshableApplicationContext类中调用了该方法, 我们继续跟进这个类中看:

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		if (this.allowBeanDefinitionOverriding != null) {
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.allowCircularReferences != null) {
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
	}
复制代码

发现是在customizeBeanFactory()方法中调用的,接着我们来跟踪this.allowBeanDefinitionOverriding变量,看看是在哪里设置的:

/**
	 * Set whether it should be allowed to override bean definitions by registering
	 * a different definition with the same name, automatically replacing the former.
	 * If not, an exception will be thrown. Default is "true".
	 * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
	 */
	public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
		this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
	}
复制代码

可以看到AbstractRefreshableApplicationContext暴露了setAllowBeanDefinitionOverriding()方法来设置allowBeanDefinitionOverriding变量的值。 直接在AbstractRefreshableApplicationContext这个类中查看哪里调用了这个方法,发现找不到, 那我们就看看他有哪些子类。

我们会发现一个非常熟悉的类:ClassPathXmlApplicationContext 接下来就简单了,我们只要通过ClassPathXmlApplicationContext类调用父类的setAllowBeanDefinitionOverriding()方法,就可以设置allowBeanDefinitionOverriding变量的值了。

代码如下:

public static void main(String[] args){
   ClassPathXmlApplicationContext applicationContext = 
    new ClassPathXmlApplicationContext(new String[]{"context.xml", "context1.xml"}, false);
    // 注意context的顺序,可以预知肯定是在context1.xml中出现冲突
    // 注意这个false数据,设置为false,意味着不会主动的去刷新bean工厂以及解析xml
   applicationContext.setAllowBeanDefinitionOverriding(false);
   // 赋值application的参数allowBeanDefinitionOverriding
   applicationContext.refresh();
   // 现在需要手动的启动refresh操作

   Student student = (Student)applicationContext.getBean("student");
   System.out.println(student.toString());
}
复制代码

大功告成~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏全栈者

前端数据获取之Ajax与Fetch (一)

Ajax,读作”阿贾克斯“,这个是每一个web开发者必掌握的一门技术,现在咱们打开一个网页,页面上数据多多少少都会有它的一些参与,来获取数据,但也并不是所有的数...

15120
来自专栏玩转JavaEE

Spring 容器 17 个常用注解总结

传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:

11830
来自专栏Java架构学习路线

还怕不记得Spring Boot注解吗?5类注解全在这里了(建议收藏)

Spring Boot的核心就是注解。Spring Boot通过各种组合注解,极大地简化了Spring项目的搭建和开发。在Spring Boot中有一些注解是...

6300
来自专栏Java研发军团

SpringBoot之Mybatis整合实战一(附分页使用)

随后配置yml文件指定抽象Mapper接口,用于实现生成的Mapper继承改Mapper实现通用Mapper

12510
来自专栏Java识堂

图解Dubbo和ZooKeeper是如何协同工作的?

微服务是最近比较火的概念,而微服务框架目前主流的有Dubbo和Spring Cloud,两者都是为了解决微服务遇到的各种问题而产生的,即遇到的问题是一样的,但是...

13720
来自专栏Java识堂

Log4j 2配置指南

以前都是直接在项目中使用网上的log4j2.xml,大概能用就行,结果用Storm的时候,项目中的log4j2.xml文件失效,必须在Storm目录的配置文件中...

22620
来自专栏Java研发军团

SpringBoot中配置logback

spring boot内部使用Commons Logging来记录日志,但也保留外部接口可以让一些日志框架来进行实现,例如Java Util Logging,L...

71910
来自专栏Java识堂

Spring中XML,注解,JavaConfig如何选择

Spring的IOC极大的方便了我们的编程,当我们需要某个对象的时候,不在需要自己去new,只要告诉Spring一声,Spring就会把我们所需要的类准备好,就...

9820
来自专栏Java识堂

分库分表,读写分离后,数据库中间件扮演了一个怎样的角色?

前面一篇文章图解分布式系统架构(看推荐阅读)大概讲了一下分库分表,以及读写分离出现的场景,分库分表为了解决高并发和海量数据的问题。

11340
来自专栏IT那个小笔记

Tomcat服务器

sun公司定义了这么多标准,其它公司的人都可以来去根据我这些标准来做JavaEE程序

11020

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励