Spring 系列目錄(http://www.javashuo.com/article/p-kqecupyl-bm.html)php
Spring 循環引用相關文章:html
在使用 Spring 的場景中,有時會碰到以下的一種狀況,即 bean 之間的循環引用。即兩個 bean 之間互相進行引用的狀況。這時,在 Spring xml 配置文件中,就會出現以下的配置:java
<bean id="beanA" class="BeanA" p:beanB-ref="beanB" /> <bean id="beanB" class="BeanB" p:beanA-ref="beanA" />
在通常狀況下,這個配置在 Spring 中是能夠正常工做的,前提是沒有對 beanA 和 beanB 進行加強。可是,若是任意一方進行了加強,好比經過 spring 的代理對 beanA 進行了加強,即實際返回的對象和原始對象不一致的狀況,在這種狀況下,就會報以下一個錯誤:git
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Bean with name 'beanA' has been injected into other beans [beanB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:605) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at com.github.binarylei.spring.beans.factory.circle.Main.main(Main.java:13)
這個錯誤即對於一個 bean,其所引用的對象並非由 Spring 容器最終生成的對象,而只是一個原始對象,而 Spring 默認是不容許這種狀況出現,即持有過程當中間對象。那麼,這個錯誤是如何產生的,以及在 Spring 內部,是如何來檢測這種狀況的呢。這就得從 Spring 如何建立一個對象,以及如何處理 bean 間引用,以及 Spring 使用何種策略處理循環引用問題提及。github
Spring 循環依賴有如下幾種狀況:spring
(1) 存在兩個 bean 相互依賴app
public class BeanA { private BeanB beanB; } public class BeanB { private BeanA beanA; }
(2) xml 配置ide
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="beanA" class="com.github.binarylei.spring.beans.factory.circle.BeanA" p:beanB-ref="beanB"/> <bean id="beanB" class="com.github.binarylei.spring.beans.factory.circle.BeanB" p:beanA-ref="beanA"/> </beans>
(3) 正常場景源碼分析
若是不對 BeanA 進行任務加強,Spring 能夠正確處理循環依賴。post
public class Main { public static void main(String[] args) { XmlBeanFactory beanFactory = new XmlBeanFactory( new ClassPathResource("spring-context-circle.xml")); // beanFactory.addBeanPostProcessor(new CircleBeanPostProcessor()); BeanA beanA = (BeanA) beanFactory.getBean("beanA"); } }
(4) 異常場景
如今對 BeanA 用 Spring 提供的 BeanPostProcessor 進行加強處理,這樣最終獲得的 beanA 就是代理後的對象了。
public class CircleBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean instanceof BeanA ? new BeanA() : bean; } }
此時給 beanFactory 註冊一個 BeanPostProcessor 後置處理器,再次運行代碼則會拋出上述異常。
在 Spring 中初始化一個單例的 bean 有如下幾個主要的步驟:
createBeanInstance
實例化 bean 對象,通常是經過反射調用默認的構造器。populateBean
bean 屬性注入,在這個步驟會從 Spring 容器中查找對應屬性字段的值,解決循環依賴問題。initializeBean
調用的 bean 定義的初始化方法。Spring 解決循環思路是第一步建立 bean 實例後,就將這個未進行屬性注入的 bean 經過 addSingletonFactory 添加到 beanFactory 的容器中,這樣即便這個對象還未建立完成就能夠經過 getSingleton(beanName) 直接在容器中找到這個 bean。過程以下所示:
上圖展現了建立 beanA 的流程,毫無疑問在 beanA 實例化完成後經過 addSingletonFactory 將這個還未初始化的對象暴露到容器後,就能夠經過 getBean(A) 查找到了,這樣能夠解決依賴的問題了。但就真的沒有問題了嗎?Spring 又爲何要拋出上述 BeanCurrentlyInCreationException 的異常呢?
Spring 在 createBeanInstance、populateBean、initializeBean 完成 bean 的建立後,還有一個依賴檢查。以 beanA 的建立過程爲例(beanA -> beanB -> beanA)
// 1. earlySingletonExposure=true 時容許循環依賴 if (earlySingletonExposure) { // 2. 獲取容器中的提早暴露的 beanA 對象,這個對象只有在循環依賴時纔有值 // 此時這個提早暴露的 beanA 被其依賴的對象持有 eg: beanB Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 3. exposedObject = initializeBean(beanName, exposedObject, mbd) 也就是說後置處理器可能對其作了加強 // 這樣暴露先後的 beanA 可能再也不是同一個對象,Spring 默認是不容許這種狀況發生的 // 也就是 allowRawInjectionDespiteWrapping=false // 3.1 beanA 沒有被加強 if (exposedObject == bean) { exposedObject = earlySingletonReference; // 3.2 beanA 被加強 // 若是存在依賴 beanA 的對象(eg: beanB),而且這個對象已經建立,則說明未被加強的 beanA 被其它對象依賴 } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { // beanB 已經建立,則說明它依賴了未被加強的 beanA,這樣容器中實際存在兩個不一樣的 beanA 了 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } }
簡單來講就是,beanA 還未初始化完成就將這個對象暴露到 Spring 容器中了,此時建立 beanB 時會經過 getBean(A) 獲取這個還未初始化完成的 beanA。若是此後 Spring 容器沒有修改 beanA 還好,但要是以後在第三步 initializeBean 又對 beanA 進行了加強的話,此時問題來了:Spring 容器實際上有兩個 beanA,加強前和加強後的。異常就此誕生。
固然 Spring 了提供了控制是否要校驗的參數 allowRawInjectionDespiteWrapping,默認爲 false,就是不容許這種狀況發生。
知道了 BeanCurrentlyInCreationException 產生的緣由,那咱們能夠強行修復這個 Bug,固然最好的辦法是不要在代碼中出現循環依賴的場景。
public static void main(String[] args) { XmlBeanFactory beanFactory = new XmlBeanFactory( new ClassPathResource("spring-context-circle.xml")); beanFactory.addBeanPostProcessor(new CircleBeanPostProcessor()); // 關鍵 beanFactory.setAllowRawInjectionDespiteWrapping(true); BeanA beanA = (BeanA) beanFactory.getBean("beanA"); }
參考:
1 . 《Spring中循環引用的處理》:https://www.iflym.com/index.php/code/201208280001.html
天天用心記錄一點點。內容也許不重要,但習慣很重要!