Spring框架使用ByName自动注入同名问题剖析

问题描述

  我们在使用spring框架进行项目开发的时候,为了配置Bean的方便经常会使用到Spring当中的Autosire机制,Autowire根据注入规则的不同又可以分为==ByName==和==ByType==这两种机制(两者的用法和区别可以参考Spring@Autowire官方文档)。但大家在使用Autowire当中==ByName==机制的时候有没有思考过这样一个问题,当我们配置了两个name属性相同的Bean,Spring在自动注入的时候会采取怎样处理方式?会覆盖?还是抛出异常?还是其他的方式?

实例验证

  要得到上面的问题的答案,最简答的方案就是写实列进行验证咯~我们先在Spring中配置两个同名的Bean,配置信息如下:

<bean name="test1" class="com.yanxiao.Test1">
    <property name="age" value="20" />
</bean>

<bean name="test1" class="com.yanxiao.Test1">
    <property name="age" value="21" />
</bean>

  接下来运行程序加载该配置文件,会发现Spring可以正常启动,于是乎我们之前关于抛出异常的假设是错误的。在程序当中获得获得自动注入的名称为test1的Bean对象打印其age属性,发现输出的结果是21,说明==对于同名的BeanSpring会采取覆盖的策略,后面配置的Bean会覆盖掉前面配置的Bean对象。==
  看到这里我们最初的问题已经得到了解答,但这个问题难到不值得我们继续思考下去吗?Spring这种强制的覆盖措施真的合理吗,有些情况下由于开发人员的疏忽在没有意识的情况下导致了同名现象的产生,直接覆盖所导致的一些错误会为开发人员的排查错误带来很大的难度?

那么如何解决这个问题呢?靠开发人员的自律?不定义重复名称的bean?我觉得这个是非常不靠谱的,因为项目依赖可能比较复杂,开发人员不尽相同.所以我认为只有通过在程序中引入一种报错机制才能解决这个问题。

  Spring既然作为一款优秀的开源框架,其内部除了覆盖之外有没有提供其他的如报错机制等方案供我们选择呢?

源码剖析

  带着上面的问题,我们对验证程序进行单步跟踪,从源码的角度了解Spring内部的处理机制是怎样的。
  当我们跟踪当Bean注册阶段,执行DefaultListableBeanFactory类的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法时,发现有一个==allowBeanDefinitionOverriding==属性。

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

观察这段代码的逻辑我们可以发现,Spring框架中自动注入遇到同名Bean时其实提供了不同的方案供我们选择,一种是我们之前已经验证过的覆盖措施,通过观看源码我们发现:其实==采用覆盖方案的话如果我们的项目中有日志打印支持的话,Spring也会提供info级别的日志信息。==另一种便是抛出异常的方式。

项目实践

  通过上面的源码,我们知道了Spring框架提供了一种开发人员必须解决id或者name重复的问题后才能成功启动容器的自动注入处理方法,这个方案在有些保证项目高可靠的情况下还是十分有用的,那么该怎样选取这种同名抛异常的处理机制呢?
  问题的关键就在于我们应该如何改变allowBeanDefinitionOverriding属性的值,通过查阅文档了解到两种方案供大家参考:

方案一

1、自己写一个继承ContextLoaderListener的listener,比如SpringContextLoaderListener,然后重写方法customizeContext,如

public class SpringContextLoaderListener extends ContextLoaderListener {

 @Override
 protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
  super.customizeContext(servletContext, applicationContext);

  XmlWebApplicationContext context = (XmlWebApplicationContext) applicationContext;
  context.setAllowBeanDefinitionOverriding(false); //在这里将XmlWebApplicationContext属性allowBeanDefinitionOverriding设置为false,这个属性的值最终
  //会传递给DefaultListableBeanFactory类的allowBeanDefinitionOverriding属性
 }
}

2、在web.xml使用自定义的listener,配置如下:


<listener>
      <listener-class>com.zyr.web.spring.SpringContextLoaderListener</listener-class>
</listener>

  这样,在项目启动时,不同配置文件中如果有同名id或者name的bean,直接抛异常,容器停止启动,到达我们的预期效果。

  其实我们的目的仅仅是更改一个属性值,方案一这种自己写一个listener的方法还是有点麻烦。下面还有一种相较更为简便的方案

方案二

1、创建一个实现接口ApplicationContextInitializer的类,如SpringApplicationContextInitializer,代码如下:

public class SpringApplicationContextInitializer implements ApplicationContextInitializer<XmlWebApplicationContext> {


 public void initialize(XmlWebApplicationContext applicationContext) {
  applicationContext.setAllowBeanDefinitionOverriding(false);//在这里将XmlWebApplicationContext属性allowBeanDefinitionOverriding设置为false,这个属
  //性的值最终会传递给DefaultListableBeanFactory类的allowBeanDefinitionOverriding属性
 }
}

2、在web.xml文件中添加配置

<context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.zyr.web.spring.SpringApplicationContextInitializer</param-value>
 </context-param>
 
0
 
0
原文地址:https://www.cnblogs.com/writeLessDoMore/p/6822309.html