問題描述
我們在使用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