問題:
當我們的web應用做成一個大項目之后,里面有很多的bean配置,如果兩個bean的配置id是一樣的而且實現類也是一樣的,例如有下面兩份xml的配置文檔:
beancontext1.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "/spring-beans.dtd">
- <beans>
- <bean id="testbean" class="com.koubei.samebeannameconfict.Bean">
- <property name="name" value="beancontext1" />
- </bean>
- </beans>
beancontext2.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "/spring-beans.dtd">
- <beans>
- <bean id="testbean" class="com.koubei.samebeannameconfict.Bean">
- <property name="name" value="beancontext2" />
- </bean>
- </beans>
當spring容器初始化時候同時加載這兩份配置文件到當前的上下文的時候,代碼如下:
- public static void main(String[] args) {
- ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
- new String[] {
- "com/koubei/samebeannameconfict/beancontext1.xml",
- "com/koubei/samebeannameconfict/beancontext2.xml" });
- //context.setAllowBeanDefinitionOverriding(false);
- //context.refresh();
- Bean bean = (Bean) context.getBean("testbean");
- System.out.println(bean.getName());
- }
執行這個程序你會看見控制台上打印的結果是:
beancontext2
顯然,beancontext2.xml的bean的配置覆蓋了 beancontext1.xml中bean的配置,而且在spring初始化上下文的過程中這個過程是靜悄悄的執行的,連一點警告都沒有。這樣如果你的項目中定義了兩個id同名的bean,並且,他們的實現方式又是不一樣的,這樣在后期在項目中執行的邏輯看起來就會非常詭異,而且,如果有大量配置spring配置文件的話,排查問題就會非常麻煩。
解決問題:
那么,我們如何來解決這個問題嗎?靠程序員自律?絕對不定義重復名稱的bean?我覺得這個是不靠譜的,只有通過在程序中引入一種交錯機制才能解決這個問題。
首先,我們將上面那段程序的log4j日志打開,看看在spring在初始化的時候面對有兩個同名的bean是怎么處理的。
- INFO - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@aa9835: display name [org.springframework.context.support.ClassPathXmlApplicationContext@aa9835]; startup date [Sat Jun 19 18:23:30 CST 2010]; root of context hierarchy
- INFO - Loading XML bean definitions from class path resource [com/koubei/samebeannameconfict/beancontext1.xml]
- DEBUG - Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
- DEBUG - Found beans DTD [file:///spring-beans.dtd] in classpath: spring-beans.dtd
- DEBUG - Loading bean definitions
- DEBUG - Loaded 1 bean definitions from location pattern [com/koubei/samebeannameconfict/beancontext1.xml]
- INFO - Loading XML bean definitions from class path resource [com/koubei/samebeannameconfict/beancontext2.xml]
- DEBUG - Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
- DEBUG - Found beans DTD [file:///spring-beans.dtd] in classpath: spring-beans.dtd
- DEBUG - Loading bean definitions
- INFO - Overriding bean definition for bean 'testbean': replacing [Generic bean: class [com.koubei.samebeannameconfict.Bean]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [com/koubei/samebeannameconfict/beancontext1.xml]] with [Generic bean: class [com.koubei.samebeannameconfict.Bean]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [com/koubei/samebeannameconfict/beancontext2.xml]]
- DEBUG - Loaded 0 bean definitions from location pattern [com/koubei/samebeannameconfict/beancontext2.xml]
- INFO - Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@aa9835]:org.springframework.beans.factory.support.DefaultListableBeanFactory@1662dc8
- DEBUG - 1 beans defined in org.springframework.context.support.ClassPathXmlApplicationContext@aa9835: display name [org.springframework.context.support.ClassPathXmlApplicationContext@aa9835]; startup date [Sat Jun 19 18:23:30 CST 2010]; root of context hierarchy
- DEBUG - Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@1cb25f1]
- DEBUG - Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@503429]
- INFO - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1662dc8: defining beans [testbean]; root of factory hierarchy
- DEBUG - Creating shared instance of singleton bean 'testbean'
- DEBUG - Creating instance of bean 'testbean'
- DEBUG - Eagerly caching bean 'testbean' to allow for resolving potential circular references
- DEBUG - Finished creating instance of bean 'testbean'
- DEBUG - Returning cached instance of singleton bean 'testbean'
以上日志中標紅的是關鍵,spring在處理有重名的bean的定義的時候原來是使用的覆蓋(override)的方式。我們來看看它是如何覆蓋的
在org.springframework.beans.factory.support.DefaultListableBeanFactory 這個類中有這樣一段代碼:
- synchronized (this.beanDefinitionMap) {
- Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
- if (oldBeanDefinition != null) {
- if (!this.allowBeanDefinitionOverriding) {
- throw new BeanDefinitionStoreException(beanDefinition
- .getResourceDescription(), beanName,
- "Cannot register bean definition ["
- + beanDefinition + "] for bean '"
- + beanName + "': There is already ["
- + oldBeanDefinition + "] bound.");
- } else {
- if (this.logger.isInfoEnabled()) {
- this.logger
- .info("Overriding bean definition for bean '"
- + beanName + "': replacing ["
- + oldBeanDefinition + "] with ["
- + beanDefinition + "]");
- }
- }
- } else {
- this.beanDefinitionNames.add(beanName);
- this.frozenBeanDefinitionNames = null;
- }
- this.beanDefinitionMap.put(beanName, beanDefinition);
- resetBeanDefinition(beanName);
- }
spring ioc容器在加載bean的過程中會去判斷beanName 是否有重復,如果發現重復的話在根據allowBeanDefinitionOverriding 這個成員變量,如果是true的話則拋出BeanDefinitionStoreException 這個異常,如果為false的話就會覆蓋這個bean的定義。
所以,解決這個問題的辦法就比較簡單了,只要將這個allowBeanDefinitionOverriding值在spring初始化的時候設置為false就行了。
我把解決這個問題的環境放到,web工程中來:
在web工程中加載spring容器會通過:
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
這個listener來完成的,在這個listener中會構造 org.springframework.web.context.ContextLoader 這個構造器來加載bean
所以,只要擴展 ContextLoader 和ContextLoaderListener這兩個類就行了,代碼如下:
KoubeiContextLoader:
- import org.springframework.web.context.ConfigurableWebApplicationContext;
- import org.springframework.web.context.ContextLoader;
- import org.springframework.web.context.support.XmlWebApplicationContext;
- /**
- *
- *
- * @author 百歲(莫正華 baisui@taobao.com)
- * @version 1.0 2010-6-19
- */
- public class KoubeiContextLoader extends ContextLoader {
- @Override
- protected void customizeContext(ServletContext servletContext,
- ConfigurableWebApplicationContext applicationContext) {
- XmlWebApplicationContext context = (XmlWebApplicationContext) applicationContext;
- context.setAllowBeanDefinitionOverriding(false);
- }
- }
KoubeiContextLoaderListener:
- /**
- *
- *
- * @author 百歲(莫正華 baisui@taobao.com)
- * @version 1.0 2010-6-19
- */
- public class KoubeiContextLoaderListener extends ContextLoaderListener {
- @Override
- protected ContextLoader createContextLoader() {
- return new KoubeiContextLoader();
- }
- }
最后修改wen-inf 下web.xml 文件,修改listener的配置,如下:
- <listener>
- <listener-class>com.koubei.kac.springcontext.KoubeiContextLoaderListener</listener-class>
- </listener>
設置完這些就ok了,這樣你項目中如果在兩份被加載的xml文件中如果再出現名字相同的bean的話,spring在加載過程中就會無情的拋出異常,當你去除掉這個異常之后,就能重新出發了。yeah!!!!