Spring-Bean的循環依賴


文章參考:

曹工說Spring Boot源碼(29)-- Spring 解決循環依賴為什么使用三級緩存,而不是二級緩存(好文)
一文告訴你Spring是如何利用“三級緩存“巧妙解決Bean的循環依賴問題的【享學Spring】(好文)

面試講解思路:

  • 什么是循環依賴?
  • Spring怎么解決循環依賴
  • Spring對於循環依賴無法解決的場景
  • 怎么檢測循環依賴

什么是循環依賴?

循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。類似於死鎖

Spring中循環依賴場景有:
(1)構造器的循環依賴 (不能被解決,加入singletonFactories三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決 
(2)field屬性的循環依賴,也就是setter注入的循環依賴。(可以通過三級緩存來解決)

Spring對於循環依賴的解決

Spring循環依賴的理論依據其實是Java基於引用傳遞,當我們獲取到對象的引用時,對象的field或者或屬性是可以延后設置的。
Spring單例對象的初始化其實可以分為三步:

  • createBeanInstance, 實例化,其實也就是調用對象的構造方法實例化對象
  • populateBean,填充屬性,這一步主要是對bean的依賴屬性進行注入(@Autowired)
  • initializeBean,調用spring xml中指定的init方法,比如一些形如initMethodInitializingBean等方法
循環依賴主要發生在第一、第二步。也就是構造器循環依賴和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);
這三級緩存分別指: 
singletonObjects:單例對象的cache
earlySingletonObjects :提前暴光的單例對象的Cache,存放的是沒有初始化完全的對象
singletonFactories : 單例對象工廠的cache,沒有經過后置處理且沒有初始化完全的對象的工廠。加入singletonFactories三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決 ,工廠對象中,有3個field,一個是beanName,一個是RootBeanDefinition,一個是已經創建好的,但還沒有注入屬性的bean,如果在2級緩存中,還是沒找到,則在3級緩存中查找對應的工廠對象,利用拿到的工廠對象,去獲取包裝后的bean,或者說,代理后的bean。
我們在創建bean的時候,首先想到的是從cache中獲取這個單例的bean,這個緩存就是singletonObjects。主要調用方法就就是:     
// allowEarlyReference: 是否允許從singletonFactories中通過getObject拿到對象
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //    isSingletonCurrentlyInCreation()判斷當前單例bean是否正在創建中,也就是沒有初始化完成
    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()(三級緩存)獲取該對象的工廠,使用工廠獲取對象,並放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存。

比如A、B兩個bean相互依賴,他們的注入過程如下:

A首先完成了初始化的第一步,即使用無參構造創建bean,並且將自己的工廠提前曝光到singletonFactories中,然后進行初始化的第二步屬性填充,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以需要先創建B,B在屬性填充的時候發現自己依賴了對象A,於是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories獲取到了,因為A在屬性填充之前就將自己的工廠提前曝光在了ObjectFactory中,所以B能夠通過A對象的工廠拿到創建了一半的A對象,B拿到A對象后順利完成了創建對象的三個步驟,完全初始化之后將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己初始化的剩下階段,最終A也完成了初始化,被裝入了一級緩存singletonObjects中。所以循環依賴問題就被解決了。
簡單來說就是:
為了避免循環依賴,在Spring中創建bean的原則是不等bean創建完成就會將創建bean 的ObjectFactory提早曝光加入到緩存中,一旦下一個bean創建時候需要依賴上一個bean則直 接使用ObjectFactory 。然后調用AOP的后置處理器類:getEarlyBeanReference,拿到代理后的bean(假設此處切面滿足,要創建代理);隨后將對象從ObjectFactory三級緩存放入earlySingletonObjects二級緩存以提高其他對象獲取該引用的效率,因為可以少嘗試一次緩存。

原型模式(prototype)  循環依賴

因為原型模式每次都是重新生成一個全新的bean,根本沒有緩存一說。這將導致實例化A完,填充發現需要B,實例化B完又發現需要A,而每次的A又都要不一樣,所以死循環的依賴下去。唯一的做法就是利用循環依賴檢測,發現原型模式下存在循環依賴並拋出異常

Spring對於循環依賴無法解決的場景

構造器注入沒有將自己提前曝光在第三級緩存中,所以對其他依賴它的對象來說不可見,所以這種循環依賴問題不能被解決,而原型模式的循環依賴則沒有用到緩存機制,更不能解決循環依賴問題,兩種情況都會拋出循環依賴異常。

怎么檢測是否存在循環依賴

 可以 Bean創建的時候給其打個標記,如果遞歸調用回來發現正在創建中的話--->即可說明循環依賴。

 
只有單例模式下才會嘗試解決循環依賴,多例模式(原型模式)下如果存在循環依賴直接拋出異常

為什么要三級緩存,一級不行嗎

一級緩存的問題在於,就1個map,里面既有完整的已經ready的bean,也有不完整的,尚未設置field的bean。

如果這時候,有其他線程去這個map里獲取bean來用怎么辦?拿到的bean,不完整,怎么辦呢?屬性都是null,直接空指針了。

所以,我們就要加一個map,這個map,用來存放那種不完整的bean。

為什么要三級緩存,兩級不行嗎

其實粗略划分是兩種對象,已創建完成的和未創建完成的。但是再細分其實可以划分了三種對象,或者三個階段的對象,已創建完成的,未創建完成但已經后置處理過的,未創建完成且未后置處理的。
第二級緩存中存放的是沒有經過后置處理且沒有初始化完全的對象的工廠,如果把經過了后置處理的和沒經過后置處理的對象都放在第二級緩存中,這顯然會干擾我們程序的運行。所以需要先暴露在第三級緩存中,從第三級緩存中取出對象的時候判斷是否需要經過后置處理,如果需要那么經過后置處理后再放入第二級緩存。
使用三級而非二級緩存並非出於IOC的考慮,而是出於AOP的考慮,即若使用二級緩存,在AOP情形下,注入到其他bean的,不是最終的代理對象,而是原始對象。
在將三級緩存放入二級緩存的時候,getEarlyBeanReference()會判斷是否有SmartInstantiationAwareBeanPostProcessor這樣的后置處理器,判斷是否用對對這個對象進行了應用邏輯修或者應用邏輯增強,比如說判斷是否實現了AOP邏輯,如果實現了AOP邏輯,那就需要執行AOP邏輯后把代理對象放入第二級緩存,而非是原始對象。改換句話說 getEarlyBeanReference() 這里是給用戶提供接口擴展的,所以采用了三級緩存
加個三級緩存,里面不存具體的bean,里面存一個工廠對象。通過工廠對象,是可以拿到最終形態的代理后的egg。
然后調用AOP的后置處理器類:getEarlyBeanReference,拿到代理后的bean(假設此處切面滿足,要創建代理);
bean的工廠一直存在於第三級工廠緩存中,直到第一個對象需要該引用時才會獲取該工廠,並將該工廠獲取的對象放到第二級緩存中,這是實際上是起到了一種延時暴露的機制,在沒有對象需要這個引用之前,這個對象都不會出現在第二級緩存中,可能這樣可以提高性能
其實按道理是可以二級緩存就可以初始化一些內容,但是在這里
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

getEarlyBeanReference的作用:調用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()這個方法 否則啥都不做,也就是給調用者個機會,自己去實現暴露這個bean的應用的邏輯~~~,比如在getEarlyBeanReference()里可以實現AOP的邏輯~~~ 參考自動代理創建器AbstractAutoProxyCreator 實現了這個方法來創建代理對象

Spring容器會將每一個正在創建的Bean 標識符放在一個 singletonsCurrentlyInCreation“當前創建Bean池”中Bean標識符在創建過程中將一直保持在這個池中,而對於創建完畢的Bean將從當前創建Bean池中清除掉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM