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()); }
大功告成~