溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題

發(fā)布時(shí)間:2021-07-09 16:58:10 來(lái)源:億速云 閱讀:454 作者:chen 欄目:大數(shù)據(jù)

這篇文章主要介紹“如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題”,在日常操作中,相信很多人在如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

一、背景

如果再xml中配置了相同的<Bean>的ID或name可能會(huì)造成一些問(wèn)題,今天我們來(lái)探討一下并解決。

二、問(wèn)題

1、在同一個(gè)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>

這種情況下,會(huì)直接拋出異常"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會(huì)直接覆蓋text1.xml中的bean,Spring最終只會(huì)把text2.xml中的bean加載到IOC容器中。

此時(shí)spring并不會(huì)報(bào)錯(cuò),只會(huì)打印info級(jí)別的日志信息,"Overriding bean definition for bean xxx with a different definition: replacing [xxx] with [xxx]" 這種情況下,要排查問(wèn)題很困難。

三、解決

通過(guò)查看spring源碼,我們發(fā)現(xiàn)在springIOC容器初始化時(shí),有一個(gè)關(guān)鍵變量allowBeanDefinitionOverriding。

再來(lái)看下源碼: 

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

		// 校驗(yàn) beanName 與 beanDefinition 非空
		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		//校驗(yàn)解析的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);
		// 如果已經(jīng)存在
		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行,通過(guò)判斷allowBeanDefinitionOverriding變量的值,來(lái)決定是覆蓋還是拋出異常,而allowBeanDefinitionOverriding這個(gè)值默認(rèn)是true。所以默認(rèn)情況下是直接覆蓋的,不會(huì)拋出異常。

那么我們很容易就想到,把a(bǔ)llowBeanDefinitionOverriding的值改為false就可以解決問(wèn)題。

查看源碼,我們發(fā)現(xiàn)DefaultListableBeanFactory類提供了賦值allowBeanDefinitionOverriding變量的方法:

public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { 
   this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; 
}

所以我們只要調(diào)用這個(gè)方法,把a(bǔ)llowBeanDefinitionOverriding賦值成false就成功了。

那么如何修改呢? 我們來(lái)看下都有哪些類調(diào)用了setAllowBeanDefinitionOverriding()方法:

如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題

可以看到,在AbstractRefreshableApplicationContext類中調(diào)用了該方法, 我們繼續(xù)跟進(jìn)這個(gè)類中看:

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

發(fā)現(xiàn)是在customizeBeanFactory()方法中調(diào)用的,接著我們來(lái)跟蹤this.allowBeanDefinitionOverriding變量,看看是在哪里設(shè)置的:

/**
	 * 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()方法來(lái)設(shè)置allowBeanDefinitionOverriding變量的值。 直接在AbstractRefreshableApplicationContext這個(gè)類中查看哪里調(diào)用了這個(gè)方法,發(fā)現(xiàn)找不到, 那我們就看看他有哪些子類。

如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題

我們會(huì)發(fā)現(xiàn)一個(gè)非常熟悉的類:ClassPathXmlApplicationContext 接下來(lái)就簡(jiǎn)單了,我們只要通過(guò)ClassPathXmlApplicationContext類調(diào)用父類的setAllowBeanDefinitionOverriding()方法,就可以設(shè)置allowBeanDefinitionOverriding變量的值了。

代碼如下:

public static void main(String[] args){
   ClassPathXmlApplicationContext applicationContext = 
    new ClassPathXmlApplicationContext(new String[]{"context.xml", "context1.xml"}, false);
    // 注意context的順序,可以預(yù)知肯定是在context1.xml中出現(xiàn)沖突
    // 注意這個(gè)false數(shù)據(jù),設(shè)置為false,意味著不會(huì)主動(dòng)的去刷新bean工廠以及解析xml
   applicationContext.setAllowBeanDefinitionOverriding(false);
   // 賦值application的參數(shù)allowBeanDefinitionOverriding
   applicationContext.refresh();
   // 現(xiàn)在需要手動(dòng)的啟動(dòng)refresh操作

   Student student = (Student)applicationContext.getBean("student");
   System.out.println(student.toString());
}

大功告成~

到此,關(guān)于“如何解決Spring中配置id或name相同的Bean可能引發(fā)的問(wèn)題”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI