转载至:http://blog.csdn.net/linuxerlin/article/details/38778761
问题:
最近在做公共框架的构建,由于采用了模块化的插件机制,在开发的过程中,发现不同开发人员的spring配置中,出现了两个bean的配置id和实现类名称都一样的情况。
例如有下面的bean类:
package
com.XXX.common.test;
public
class
SameNameBean {
private
String
beanName
;
public
void
setBeanName(String beanName) {
this
.
beanName
= beanName;
}
public
String test() {
return
"Hello "
+
beanName
;
}
}
和两个xml的配置:
beanContext1.xml:
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
DOCTYPE
beans
PUBLIC
"-//SPRING//DTD BEAN//EN"
"/spring-beans.dtd"
>
<
beans
>
<
bean
id
=
"
testNameBean
"
class
=
"com.xxx.common.test.SameNameBean"
>
<
property
name
=
"
beanName
"
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
=
"
testNameBean
"
class
=
"com.xxx.common.test.SameNameBean"
>
<
property
name
=
"
beanName
"
value
=
"beanContext2"
/>
bean
>
beans
>
当spring容器初始化时候同时加载这两份配置文件到当前的上下文的时候,代码如下:
public
class
TestBean {
public
static
void
main(String[] args) {
ClassPathXmlApplicationContext context =
new
ClassPathXmlApplicationContext(
new
String[] {
"classpath*:beanContext*.xml"
});
//context.setAllowBeanDefinitionOverriding(false);
//context.refresh();
SameNameBean sameNameBean = (SameNameBean) context
.getBean(
"testNameBean"
);
System.
out
.println(sameNameBean.test());
}
}
执行结果是:
Hello beanContext2
从结果可以看出,beanContext2.xml的bean的配置覆盖了beanContext1.xml中bean的配置,而且在spring初始化上下文的过程中没有任何的警告。这样如果在项目中定义了两个id同名的bean,并且他们的实现方式类又是不一样的,在项目的业务逻辑看起来就会非常诡异,而且,如果也不利于排除问题。
解决问题:
那么,我们如何来解决这个问题吗?靠程序员自律?绝对不定义重复名称的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 2014]; root of context hierarchy
- INFO - Loading XML bean definitions from class path resource [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 [beanContext1.xml]
- INFO - Loading XML bean definitions from class path resource [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 'testNameBean': replacing [Generic bean: class [com.xxx.common.test.SameNameBean]; 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 [beanContext1.xml]] with [Generic bean: class [com.xxx.common.test.SameNameBean]; 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 [beanContext2.xml]]
- DEBUG - Loaded 0 bean definitions from location pattern [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 2014]; 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 [testNameBean]; root of factory hierarchy
- DEBUG - Creating shared instance of singleton bean 'testNameBean'
- DEBUG - Creating instance of bean 'testNameBean'
- DEBUG - Eagerly caching bean 'testNameBean' to allow for resolving potential circular references
- DEBUG - Finished creating instance of bean 'testNameBean'
- DEBUG - Returning cached instance of singleton bean 'testNameBean'
以上日志中标红的是关键,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 这个成员变量,如果是false的话则抛出BeanDefinitionStoreException 这个异常,如果为true的话就会覆盖这个bean的定义。
所以,解决这个问题的办法就比较简单了,只要将这个allowBeanDefinitionOverriding值在spring初始化的时候设置为false就行了。
在web工程中加载spring容器,一般都会通过加载下面的
listener:
org.springframework.web.context.ContextLoaderListener
在这个listener中会构造 org.springframework.web.context.ContextLoader 这个构造器来加载bean。所以,只要扩展 ContextLoader 和ContextLoaderListener这两个类就行了,代码如下:
CustomSpringContextLoader:
package
com.xxx.itpub.contrl.context;
import
javax.servlet.ServletContext;
import
org.springframework.web.context.ConfigurableWebApplicationContext;
import
org.springframework.web.context.ContextLoader;
import
org.springframework.web.context.support.XmlWebApplicationContext;
public
class
CustomSpringContextLoader
extends
ContextLoader {
@Override
protected
void
customizeContext(ServletContext servletContext,
ConfigurableWebApplicationContext applicationContext) {
XmlWebApplicationContext context = (XmlWebApplicationContext) applicationContext;
context.setAllowBeanDefinitionOverriding(
false
);
}
}
CustomSpringContextLoaderListener :
package
com.xxx.itpub.contrl.listener;
import
org.springframework.web.context.ContextLoader;
import
org.springframework.web.context.ContextLoaderListener;
import
com.eshore.itpub.contrl.context.CustomSpringContextLoader;
public
class
CustomSpringContextLoaderListener
extends
ContextLoaderListener {
@Override
protected
ContextLoader createContextLoader() {
return
new
CustomSpringContextLoader();
}
}
最后修改WEB-INF下web.xml 文件,修改listener的配置,如下:
<
listener
>
<
listener-class
>
com.xxx.itpub.contrl.listener.CustomSpringContextLoaderListener
listener-class
>
listener
>
设置完就ok了,这样被加载的xml文件中如果再出现名字相同的bean,spring在加载过程中就会抛出下面的异常:
[2014-08-07 19:32:04.329]-[ERROR] org.springframework.web.context.ContextLoader Context initialization failed
org.springframework.beans.factory.parsing.BeanDefinitionParsingException
: Configuration problem: Failed to register bean definition with name 'publicDAO'
Offending resource: file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\dbContext.xml]; nested exception is
org.springframework.beans.factory.BeanDefinitionStoreException
: Invalid bean definition with name 'publicDAO' defined in file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\dbContext.xml]: Cannot register bean definition [Generic bean: class [com.xxx.itpub.daopub.database.impl.PublicDAOImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\dbContext.xml]] for bean 'publicDAO': There is already [Generic bean: class [com.xxx.itpub.daopub.database.impl.PublicDAOImpl]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\beansContext.xml]] bound.
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(
FailFastProblemReporter.java:68
)
at org.springframework.beans.factory.parsing.ReaderContext.error(
ReaderContext.java:85
)
at org.springframework.beans.factory.parsing.ReaderContext.error(
ReaderContext.java:76
)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(
DefaultBeanDefinitionDocumentReader.java:320
)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(
DefaultBeanDefinitionDocumentReader.java:203
)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(
DefaultBeanDefinitionDocumentReader.java:182
)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(
DefaultBeanDefinitionDocumentReader.java:139
)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(
DefaultBeanDefinitionDocumentReader.java:108
)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(
XmlBeanDefinitionReader.java:493
)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(
XmlBeanDefinitionReader.java:390
)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(
XmlBeanDefinitionReader.java:334
)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(
XmlBeanDefinitionReader.java:302
)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(
AbstractBeanDefinitionReader.java:174
)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(
AbstractBeanDefinitionReader.java:209
)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(
AbstractBeanDefinitionReader.java:180
)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(
XmlWebApplicationContext.java:125
)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(
XmlWebApplicationContext.java:94
)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(
AbstractRefreshableApplicationContext.java:130
)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(
AbstractApplicationContext.java:537
)
at org.springframework.context.support.AbstractApplicationContext.refresh(
AbstractApplicationContext.java:451
)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(
ContextLoader.java:389
)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(
ContextLoader.java:294
)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(
ContextLoaderListener.java:112
)
at org.apache.catalina.core.StandardContext.listenerStart(
StandardContext.java:3843
)
at org.apache.catalina.core.StandardContext.start(
StandardContext.java:4350
)
at org.apache.catalina.core.ContainerBase.addChildInternal(
ContainerBase.java:791
)
at org.apache.catalina.core.ContainerBase.addChild(
ContainerBase.java:771
)
at org.apache.catalina.core.StandardHost.addChild(
StandardHost.java:525
)
at org.apache.catalina.startup.HostConfig.deployDirectory(
HostConfig.java:924
)
at org.apache.catalina.startup.HostConfig.deployDirectories(
HostConfig.java:887
)
at org.apache.catalina.startup.HostConfig.deployApps(
HostConfig.java:492
)
at org.apache.catalina.startup.HostConfig.start(
HostConfig.java:1147
)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(
HostConfig.java:311
)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(
LifecycleSupport.java:117
)
at org.apache.catalina.core.ContainerBase.start(
ContainerBase.java:1053
)
at org.apache.catalina.core.StandardHost.start(
StandardHost.java:719
)
at org.apache.catalina.core.ContainerBase.start(
ContainerBase.java:1045
)
at org.apache.catalina.core.StandardEngine.start(
StandardEngine.java:443
)
at org.apache.catalina.core.StandardService.start(
StandardService.java:516
)
at org.apache.catalina.core.StandardServer.start(
StandardServer.java:710
)
at org.apache.catalina.startup.Catalina.start(
Catalina.java:578
)
at sun.reflect.NativeMethodAccessorImpl.invoke0(
Native Method
)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.catalina.startup.Bootstrap.start(
Bootstrap.java:288
)
at org.apache.catalina.startup.Bootstrap.main(
Bootstrap.java:413
)
Caused by:
org.springframework.beans.factory.BeanDefinitionStoreException
: Invalid bean definition with name 'publicDAO' defined in file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\dbContext.xml]: Cannot register bean definition [Generic bean: class [com.eshore.itpub.daopub.database.impl.PublicDAOImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\dbContext.xml]] for bean 'publicDAO': There is already [Generic bean: class [com.xxx.itpub.daopub.database.impl.PublicDAOImpl]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\software\dev\apache-tomcat-6.0.16\webapps\itpub_web\WEB-INF\classes\conf\spring\beansContext.xml]] bound.
at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(
DefaultListableBeanFactory.java:657
)
at org.springframework.beans.factory.support.BeanDefinitionReaderUtils.registerBeanDefinition(
BeanDefinitionReaderUtils.java:148
)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(
DefaultBeanDefinitionDocumentReader.java:317
)
... 43 more
2014-8-7 19:32:04 org.apache.catalina.core.StandardContext listenerStart
严重: Exception sending context initialized event to listener instance of class com.eshore.itpub.contrl.listener.CustomSpringContextLoaderListener
org.springframework.beans.factory.parsing.BeanDefinitionParsingException
: Configuration problem: Failed to register bean definition with name 'publicDAO'
此时根据异常信息,去除掉同名的id之后,就可以了。
对于自动识别Autowired的同样有效。