javascript
Spring-bean的循环依赖以及解决方式___Spring源码初探--Bean的初始化-循环依赖的解决
本文主要是分析Spring bean的循環依賴,以及Spring的解決方式。 通過這種解決方式,我們可以應用在我們實際開發項目中。
1. 什么是循環依賴?
循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴于B,B依賴于C,C又依賴于A。如下圖:
注意,這里不是函數的循環調用,是對象的相互依賴關系。循環調用其實就是一個死循環,除非有終結條件。
Spring中循環依賴場景有:
(1)構造器的循環依賴
(2)field屬性的循環依賴。
2. 怎么檢測是否存在循環依賴
檢測循環依賴相對比較容易,Bean在創建的時候可以給該Bean打標,如果遞歸調用回來發現正在創建中的話,即說明了循環依賴了。
3. Spring怎么解決循環依賴
Spring的循環依賴的理論依據其實是基于Java的引用傳遞,當我們獲取到對象的引用時,對象的field或則屬性是可以延后設置的(但是構造器必須是在獲取引用之前)。
Spring的單例對象的初始化主要分為三步:
(1)createBeanInstance:實例化,其實也就是調用對象的構造方法實例化對象
(2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充
(3)initializeBean:調用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環依賴主要發生在第一、第二部。也就是構造器循環依賴和field循環依賴。
那么我們要解決循環引用也應該從初始化過程著手,對于單例來說,在Spring容器整個生命周期內,有且只有一個對象,所以很容易想到這個對象應該存在Cache中,Spring為了解決單例的循環依賴問題,使用了三級緩存。
首先我們看源碼,三級緩存主要指:
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);/** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);/** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);12345678這三級緩存分別指:
singletonFactories : 單例對象工廠的cache
earlySingletonObjects :提前暴光的單例對象的Cache
singletonObjects:單例對象的cache
我們在創建bean的時候,首先想到的是從cache中獲取這個單例的bean,這個緩存就是singletonObjects。主要調用方法就就是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return (singletonObject != NULL_OBJECT ? singletonObject : null); }上面的代碼需要解釋兩個參數:
- isSingletonCurrentlyInCreation()判斷當前單例bean是否正在創建中,也就是沒有初始化完成(比如A的構造器依賴了B對象所以得先去創建B對象, 或則在A的populateBean過程中依賴了B對象,得先去創建B對象,這時的A就是處于創建中的狀態。)
- allowEarlyReference 是否允許從singletonFactories中通過getObject拿到對象
分析getSingleton()的整個過程,Spring首先從一級緩存singletonObjects中獲取。如果獲取不到,并且對象正在創建中,就再從二級緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級緩存singletonFactory.getObject()(三級緩存)獲取,如果獲取到了則:
this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);12從singletonFactories中移除,并放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存。
從上面三級緩存的分析,我們可以知道,Spring解決循環依賴的訣竅就在于singletonFactories這個三級cache。這個cache的類型是ObjectFactory,定義如下:
public interface ObjectFactory<T> {T getObject() throws BeansException; }這個接口在下面被引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}} }這里就是解決循環依賴的關鍵,這段代碼發生在createBeanInstance之后,也就是說單例對象此時已經被創建出來(調用了構造器)。這個對象已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用。
這樣做有什么好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環依賴的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,進去了一級緩存singletonObjects中,而且更加幸運的是,由于B拿到了A的對象引用,所以B現在hold住的A對象完成了初始化。
知道了這個原理時候,肯定就知道為啥Spring不能解決“A的構造方法中依賴了B的實例對象,同時B的構造方法中依賴了A的實例對象”這類問題了!因為加入singletonFactories三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決。
Spring源碼初探–Bean的初始化-循環依賴的解決
前言
在實際工作中,經常由于設計不佳或者各種因素,導致類之間相互依賴。這些類可能單獨使用時不會出問題,但是在使用Spring進行管理的時候可能就會拋出BeanCurrentlyInCreationException等異常 。當拋出這種異常時表示Spring解決不了該循環依賴,本文將簡要說明Spring對于循環依賴的解決方法。
循環依賴的產生和解決的前提
循環依賴的產生可能有很多種情況,例如:
- A的構造方法中依賴了B的實例對象,同時B的構造方法中依賴了A的實例對象
- A的構造方法中依賴了B的實例對象,同時B的某個field或者setter需要A的實例對象,以及反之
- A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象,以及反之
當然,Spring對于循環依賴的解決不是無條件的,首先前提條件是針對scope單例并且沒有顯式指明不需要解決循環依賴的對象,而且要求該對象沒有被代理過。同時Spring解決循環依賴也不是萬能,以上三種情況只能解決兩種,第一種在構造方法中相互依賴的情況Spring也無力回天。結論先給在這,下面來看看Spring的解決方法,知道了解決方案就能明白為啥第一種情況無法解決了。
Spring對于循環依賴的解決
Spring循環依賴的理論依據其實是Java基于引用傳遞,當我們獲取到對象的引用時,對象的field或者或屬性是可以延后設置的。
Spring單例對象的初始化其實可以分為三步:
- createBeanInstance, 實例化,實際上就是調用對應的構造方法構造對象,此時只是調用了構造方法,spring xml中指定的property并沒有進行populate
- populateBean,填充屬性,這步對spring xml中指定的property進行populate
- initializeBean,調用spring xml中指定的init方法,或者AfterPropertiesSet方法
會發生循環依賴的步驟集中在第一步和第二步。
三級緩存
對于單例對象來說,在Spring的整個容器的生命周期內,有且只存在一個對象,很容易想到這個對象應該存在Cache中,Spring大量運用了Cache的手段,在循環依賴問題的解決過程中甚至使用了“三級緩存”。
“三級緩存”主要是指
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);從字面意思來說:singletonObjects指單例對象的cache,singletonFactories指單例對象工廠的cache,earlySingletonObjects指提前曝光的單例對象的cache。以上三個cache構成了三級緩存,Spring就用這三級緩存巧妙的解決了循環依賴問題。
解決方法
回想上篇文章中關于Bean創建的過程,首先Spring會嘗試從緩存中獲取,這個緩存就是指singletonObjects,主要調用的方法是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return (singletonObject != NULL_OBJECT ? singletonObject : null);}首先解釋兩個參數:
- isSingletonCurrentlyInCreation 判斷對應的單例對象是否在創建中,當單例對象沒有被初始化完全(例如A定義的構造函數依賴了B對象,得先去創建B對象,或者在populatebean過程中依賴了B對象,得先去創建B對象,此時A處于創建中)
- allowEarlyReference 是否允許從singletonFactories中通過getObject拿到對象
分析getSingleton的整個過程,Spring首先從singletonObjects(一級緩存)中嘗試獲取,如果獲取不到并且對象在創建中,則嘗試從earlySingletonObjects(二級緩存)中獲取,如果還是獲取不到并且允許從singletonFactories通過getObject獲取,則通過singletonFactory.getObject()(三級緩存)獲取。如果獲取到了則
this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName);則移除對應的singletonFactory,將singletonObject放入到earlySingletonObjects,其實就是將三級緩存提升到二級緩存中!
Spring解決循環依賴的訣竅就在于singletonFactories這個cache,這個cache中存的是類型為ObjectFactory,其定義如下:
public interface ObjectFactory<T> {T getObject() throws BeansException;}在bean創建過程中,有兩處比較重要的匿名內部類實現了該接口。一處是
new ObjectFactory<Object>() {@Override public Object getObject() throws BeansException {try {return createBean(beanName, mbd, args);} catch (BeansException ex) {destroySingleton(beanName);throw ex;} }在上文已經提到,Spring利用其創建bean(這樣做真的很不明確呀…)
另一處就是:
addSingletonFactory(beanName, new ObjectFactory<Object>() {@Override public Object getObject() throws BeansException {return getEarlyBeanReference(beanName, mbd, bean);}});此處就是解決循環依賴的關鍵,這段代碼發生在createBeanInstance之后,也就是說單例對象此時已經被創建出來的。這個對象已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用。
這樣做有什么好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環依賴的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,長大成人,進去了一級緩存singletonObjects中,而且更加幸運的是,由于B拿到了A的對象引用,所以B現在hold住的A對象也蛻變完美了!一切都是這么神奇!!
知道了這個原理時候,肯定就知道為啥Spring不能解決“A的構造方法中依賴了B的實例對象,同時B的構造方法中依賴了A的實例對象”這類問題了!
總結
Spring通過三級緩存加上“提前曝光”機制,配合Java的對象引用原理,比較完美地解決了某些情況下的循環依賴問題!
總結
以上是生活随笔為你收集整理的Spring-bean的循环依赖以及解决方式___Spring源码初探--Bean的初始化-循环依赖的解决的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rust(66)-rust智能指针与类型
- 下一篇: Spring MVC 执行过程原理(请求