您的位置:首页 > 编程语言 > Java开发

spring bean id重复覆盖的问题解决

2013-07-14 10:05 381 查看
问题:

当我们的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!!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: