问题提问:

 


问题分析:

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.xxx.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.xxx.Bean">  
       <property name="name" value="beancontext2" />  
   </bean>  
</beans>


当spring容器初始化时候同时加载这两份配置文件到当前的上下文的时候,代码如下:

public static void main(String[] args) {  
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(  
new String[] {  
"com/xxx/beancontext1.xml",  
"com/xxx/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配置文件的话,排查问题就会非常麻烦。


解决问题:


  首先,我们将上面那段程序的log4j日志打开,看看在spring在初始化的时候面对有两个同名的bean是怎么处理的。


ontext@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]
pl]
 - 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]
pl]
 - 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.xxx.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.xxx.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]
ontext@aa9835]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1662dc8
ontext@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]
ter with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@503429]
ry@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)的方式。我们来看看它是如何覆盖的

ry 这个类中有这样一段代码:

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);  
}

allowBeanDefinitionOverriding 这个成员变量,如果是true的话则抛出BeanDefinitionStoreException 这个异常,如果为false的话就会覆盖这个bean的定义。

iding值在spring初始化的时候设置为false就行了。

我把解决这个问题的环境放到,web工程中来:

在web工程中加载spring容器会通过:

<listener>  
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>

这个listener来完成的,在这个listener中会构造 org.springframework.web.context.ContextLoader 这个构造器来加载bean

所以,只要继承扩展org.springframework.web.context.ContextLoaderListener这类就行了,代码如下:

解决方案:

CustomContextLoader:

import org.springframework.web.context.ConfigurableWebApplicationContext;  
import org.springframework.web.context.ContextLoader;  
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.xwork.StringUtils;

public class CustomContextLoader extends ContextLoader {  
 
@Override  
protected void customizeContext(ServletContext servletContext,  
           ConfigurableWebApplicationContext applicationContext) {  
String allowBeanDefinitionOverriding  =iding");
if(StringUtils.isNotBlank(allowBeanDefinitionOverriding)) {
  XmlWebApplicationContext context = (XmlWebApplicationContext) applicationContext;  
context.setAllowBeanDefinitionOverriding(BooleanUtils.toBoolean(allowBeanDefinitionOverriding)); 
}
}
}

最后修改web.xml 文件,修改listener的配置,如下:

<context-param>
<param-name>AllowBeanDefinitionOverriding</param-name>
<param-value>true|false</param-value>
</context-param>
<listener>  
ner</listener-class>  
</listener>

设置完这些就ok了,这样你项目中如果在两份被加载的xml文件中如果再出现名字相同的bean的话,spring在加载过程中如果设置的是true就忽略覆盖,如果是false就会无情的抛出异常。ok!