Spring中配置id或name相同的Bean可能引起的問題及解決方案

1、背景java

若是再xml中配置了相同的<Bean>的ID或name可能會形成一些問題,今天咱們來探討一下並解決。spring

2、問題緩存

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

<?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."ide

二、在不一樣的xml中配置相同的bean的id。EX: ui

test1.xmlthis

<?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.xmldebug

<?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]" 這種狀況下,要排查問題很困難。code

3、解決

經過查看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());
}

 

大功告成~

相關文章
相關標籤/搜索