Spring beanid 同名覆盖问题解决

转载至: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:
<listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

  在这个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的同样有效。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值