最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring三級緩存解決循環(huán)依賴的過程分析

 更新時間:2023年04月03日 09:16:57   作者:我叫小八  
這篇文章主要介紹了Spring三級緩存解決循環(huán)依賴,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

循環(huán)依賴

什么是循環(huán)依賴?很簡單,看下方的代碼就知曉了

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

上面這兩種方式都是循環(huán)依賴,應(yīng)該很好理解,當(dāng)然也可以是三個 Bean 甚至更多的 Bean 相互依賴,原理都是一樣的,今天我們主要分析兩個 Bean 的依賴。

在這里插入圖片描述

這種循環(huán)依賴可能會產(chǎn)生問題,例如 A 要依賴 B,發(fā)現(xiàn) B 還沒創(chuàng)建。
于是開始創(chuàng)建 B ,創(chuàng)建的過程發(fā)現(xiàn) B 要依賴 A, 而 A 還沒創(chuàng)建好呀,因為它要等 B 創(chuàng)建好。
就這樣它們倆就擱這卡 bug 了。

解決思路

上面這種循環(huán)依賴在實際場景中是會出現(xiàn)的,所以 Spring 需要解決這個問題,那如何解決呢?
關(guān)鍵就是提前暴露未完全創(chuàng)建完畢的 Bean。
在 Spring 中,只有同時滿足以下兩點才能解決循環(huán)依賴的問題:

依賴的 Bean 必須都是單例依賴注入的方式,必須不全是構(gòu)造器注入,且 beanName 字母序在前的不能是構(gòu)造器注入

Spring 只支持單例的循環(huán)依賴,因為如果兩個 Bean 都是原型模式的話:
創(chuàng)建 A1 需要創(chuàng)建一個 B1。
創(chuàng)建 B1 的時候要創(chuàng)建一個 A2。
創(chuàng)建 A2 又要創(chuàng)建一個 B2。
創(chuàng)建 B2 又要創(chuàng)建一個 A3。
創(chuàng)建 A3 又要創(chuàng)建一個 B3…
就又卡 BUG 了,是吧,因為原型模式都需要創(chuàng)建新的對象,不能跟用以前的對象。
如果是單例的話,創(chuàng)建 A 需要創(chuàng)建 B,而創(chuàng)建的 B 需要的是之前的個 A, 不然就不叫單例了,對吧?
也是基于這點, Spring 就能操作操作了。
具體做法就是:先創(chuàng)建 A,此時的 A 是不完整的(沒有注入 B),用個 map 保存這個不完整的 A,再創(chuàng)建 B ,B 需要 A。
所以從那個 map 得到“不完整”的 A,此時的 B 就完整了,然后 A 就可以注入 B,然后 A 就完整了,B 也完整了,且它們是相互依賴的。

那為啥必須不全是構(gòu)造器注入,因為在 Spring 中創(chuàng)建 Bean 分三步:

  • 實例化,createBeanInstance,就是 new 了個對象
  • 屬性注入,populateBean, 就是 set 一些屬性值
  • 初始化,initializeBean,執(zhí)行一些 aware 接口中的方法,initMethod,AOP代理等

明確了上面這三點,再結(jié)合我上面說的“不完整的”,我們來理一下。
如果全是構(gòu)造器注入,比如A(B b),那表明在 new 的時候,就需要得到 B,此時需要 new B 。
但是 B 也是要在構(gòu)造的時候注入 A ,即B(A a),這時候 B 需要在一個 map 中找到不完整的 A ,發(fā)現(xiàn)找不到。
為什么找不到?因為 A 還沒 new 完呢,所以找到不完整的 A,因此如果全是構(gòu)造器注入的話,那么 Spring 無法處理循環(huán)依賴。

解決流程

經(jīng)過上面的鋪墊,我想你對 Spring 如何解決循環(huán)依賴應(yīng)該已經(jīng)有點感覺了,接下來我們就來看看它到底是如何實現(xiàn)的。
明確了 Spring 創(chuàng)建 Bean 的三步驟之后,我們再來看看它為單例搞的三個 map:

  • 一級緩存,singletonObjects,存儲所有已創(chuàng)建完畢的單例 Bean (完整的 Bean)
  • 二級緩存,earlySingletonObjects,存儲所有僅完成實例化,但還未進行屬性注入和初始化的 Bean
  • 三級緩存,singletonFactories,存儲能建立這個 Bean 的一個工廠,通過工廠能獲取這個 Bean,延遲化 Bean 的生成,工廠生成的 Bean 會塞入二級緩存

在這里插入圖片描述

這三個 map 是如何配合的呢?

  • 首先,獲取單例 Bean 的時候會通過 BeanName 先去 singletonObjects(一級緩存) 查找完整的 Bean,如果找到則直接返回,否則進行步驟 2。
  • 看對應(yīng)的 Bean 是否在創(chuàng)建中,如果不在直接返回找不到,如果是,則會去 earlySingletonObjects (二級緩存)查找 Bean,如果找到則返回,否則進行步驟 3
  • 去 singletonFactories (三級緩存)通過 BeanName 查找到對應(yīng)的工廠,如果存著工廠則通過工廠創(chuàng)建 Bean ,并且放置到 earlySingletonObjects 中。
  • 如果三個緩存都沒找到,則返回 null。

從上面的步驟我們可以得知,如果查詢發(fā)現(xiàn) Bean 還未創(chuàng)建,到第二步就直接返回 null,不會繼續(xù)查二級和三級緩存。
返回 null 之后,說明這個 Bean 還未創(chuàng)建,這個時候會標記這個 Bean 正在創(chuàng)建中,然后再調(diào)用 createBean 來創(chuàng)建 Bean,而實際創(chuàng)建是調(diào)用方法 doCreateBean。
doCreateBean 這個方法就會執(zhí)行上面我們說的三步驟:

  • 實例化
  • 屬性注入
  • 初始化

以上面的例子來講解,在實例化A 之后,會往三級緩存 singletonFactories 塞入一個工廠A,而調(diào)用這個工廠A的 getObject 方法,就能得到這個 A。
要注意,此時 Spring 是不知道會不會有循環(huán)依賴發(fā)生的,但是它不管,反正往 singletonFactories 塞這個工廠,這里就是提前暴露。
然后就開始執(zhí)行屬性注入,這個時候 A 發(fā)現(xiàn)需要注入 B,所以去 getBean(B),此時又會走一遍上面描述的邏輯,到了 B 的屬性注入這一步。
此時 B 調(diào)用 getBean(A),這時候一級緩存里面找不到,但是發(fā)現(xiàn) A 正在創(chuàng)建中的,于是去二級緩存找,發(fā)現(xiàn)沒找到,于是去三級緩存找,然后找到了。
并且通過上面提前在三級緩存里暴露的工廠得到 A,然后將這個工廠從三級緩存里刪除,并將 A 加入到二級緩存中。
然后結(jié)果就是 B 屬性注入成功。
緊接著 B 調(diào)用 initializeBean 初始化,最終返回,此時 B 已經(jīng)被加到了一級緩存里 。
這時候就回到了 A 的屬性注入,此時注入了 B,接著執(zhí)行初始化,最后 A 也會被加到一級緩存里,且從二級緩存中刪除 A。
Spring 解決依賴循環(huán)就是按照上面所述的邏輯來實現(xiàn)的。
重點就是在對象實例化之后,都會在三級緩存里加入一個工廠,提前對外暴露還未完整的 Bean,這樣如果被循環(huán)依賴了,對方就可以利用這個工廠得到一個不完整的 Bean,破壞了循環(huán)的條件。

二個緩存不行?

上面都說了那么多了,那我們思考下,解決循環(huán)依賴需要三級緩存嗎?
很明顯,如果僅僅只是為了破解循環(huán)依賴,二個緩存夠了,壓根就不必要三級。
你思考一下,在實例化 Bean A 之后,我在二級 map 里面塞入這個 A,然后繼續(xù)屬性注入。
發(fā)現(xiàn) A 依賴 B 所以要創(chuàng)建 Bean B,這時候 B 就能從二級 map 得到 A ,完成 B 的建立之后, A 自然而然能完成。
所以為什么要搞個三級緩存,且里面存的是創(chuàng)建 Bean 的工廠呢?
我們來看下調(diào)用工廠的 getObject 到底會做什么,實際會調(diào)用下面這個方法:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

重點就在中間的判斷,如果 false,返回就是參數(shù)傳進來的 bean,沒任何變化。
如果是 true 說明有 InstantiationAwareBeanPostProcessors 。
且循環(huán)的是 smartInstantiationAware 類型,如有這個 BeanPostProcessor 說明 Bean 需要被 aop 代理。
我們都知道如果有代理的話,那么我們想要直接拿到的是代理對象。
也就是說如果 A 需要被代理,那么 B 依賴的 A 是已經(jīng)被代理的 A,所以我們不能返回 A 給 B,而是返回代理的 A 給 B。
這個工廠的作用就是判斷這個對象是否需要代理,如果否則直接返回,如果是則返回代理對象。
看到這明白的小伙伴肯定會問,那跟三級緩存有什么關(guān)系,我可以在要放到二級緩存的時候判斷這個 Bean 是否需要代理,如果要直接放代理的對象不就完事兒了。
是的,這個思路看起來沒任何問題,問題就出在時機,這跟 Bean 的生命周期有關(guān)系。
Spring 原本的設(shè)計是,bean 的創(chuàng)建過程分三個階段:
1 創(chuàng)建實例 createBeanInstance – 創(chuàng)建出 bean 的原始對象
2 填充依賴 populateBean – 利用反射,使用 BeanWrapper 來設(shè)置屬性值
3 initializeBean – 執(zhí)行 bean 創(chuàng)建后的處理,包括 AOP 對象的產(chǎn)生
在沒有循環(huán)依賴的場景下:第 1,2 步都是 bean 的原始對象,第 3 步 initializeBean 時,才會生成 AOP 代理對象。
所以,循環(huán)依賴打破了 AOP 代理 bean 生成的時機,需要在 populateBean 之前就生成 AOP 代理 bean。
而且,生成 AOP 代理需要執(zhí)行 BeanPostProcessor,而 Spring 原本的設(shè)計是在第 3 步 initializeBean 時才去調(diào)用 BeanPostProcessor 的。
所以 Spring 先在一個三級緩存放置一個工廠,用于代表Bean的引用。只有在上面的第三步時,才會通過這個工廠去創(chuàng)建代理對象,這樣生命周期就不會亂套了。

理論上來說,使用二級緩存是可以解決 AOP 代理 bean 的循環(huán)依賴的。只是 Spring 沒有選擇這樣去實現(xiàn)。Spring 選擇了三級緩存來實現(xiàn),讓 bean 的創(chuàng)建流程更加符合常理,更加清晰明了。

總結(jié)

好了,看到這里想必你應(yīng)該對 Spring 的循環(huán)依賴很清晰了,并且面試的時候肯定也難不倒你了。
我稍微總結(jié)下:

  • 有構(gòu)造器注入,不一定會產(chǎn)生問題,具體得看是否都是構(gòu)造器注和 BeanName 的字母序
  • 如果單純?yōu)榱舜蚱蒲h(huán)依賴,不需要三級緩存,兩級就夠了。
  • 三級緩存是否為延遲代理的創(chuàng)建,盡量不打破 Bean 的生命周期

到此這篇關(guān)于Spring三級緩存解決循環(huán)依賴的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java工程師進階之MyBatis延遲加載的使用

    java工程師進階之MyBatis延遲加載的使用

    本文是java工程師進階篇,主要介紹了java應(yīng)用開發(fā)中MyBatis延遲加載及如何使用,有需要的朋友 可以借鑒參考下,希望能夠有所幫助
    2021-09-09
  • java實現(xiàn)俄羅斯方塊小程序

    java實現(xiàn)俄羅斯方塊小程序

    這篇文章主要為大家詳細介紹了java實現(xiàn)俄羅斯方塊,在eclipse環(huán)境下編寫的俄羅斯方塊小程序,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • 五種Java多線程同步的方法

    五種Java多線程同步的方法

    這篇文章主要為大家詳細介紹了五種Java多線程同步的方法,需要的朋友可以參考下
    2015-09-09
  • java中歸并排序和Master公式詳解

    java中歸并排序和Master公式詳解

    大家好,本篇文章主要講的是java中歸并排序和Master公式詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • Java中死鎖的原理實戰(zhàn)分析

    Java中死鎖的原理實戰(zhàn)分析

    這篇文章主要介紹了Java中死鎖的原理,結(jié)合具體案例形式分析了java死鎖形成的相關(guān)原理,需要的朋友可以參考下
    2019-08-08
  • MyBatis中的JdbcType映射使用詳解

    MyBatis中的JdbcType映射使用詳解

    這篇文章主要介紹了MyBatis中的JdbcType映射使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • Spring?AOP原理及動態(tài)代理

    Spring?AOP原理及動態(tài)代理

    這篇文章主要介紹了Spring?AOP原理及動態(tài)代理,文章通過圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • Spring超詳細講解事務(wù)和事務(wù)傳播機制

    Spring超詳細講解事務(wù)和事務(wù)傳播機制

    Spring事務(wù)的本質(zhì)就是對數(shù)據(jù)庫事務(wù)的支持,沒有數(shù)據(jù)庫事務(wù),Spring是無法提供事務(wù)功能的。Spring只提供統(tǒng)一的事務(wù)管理接口,具體實現(xiàn)都是由數(shù)據(jù)庫自己實現(xiàn)的,Spring會在事務(wù)開始時,根據(jù)當(dāng)前設(shè)置的隔離級別,調(diào)整數(shù)據(jù)庫的隔離級別,由此保持一致
    2022-06-06
  • Java多線程實現(xiàn)之Callable詳解

    Java多線程實現(xiàn)之Callable詳解

    這篇文章主要介紹了Java多線程實現(xiàn)之Callable詳解,Callable是一個接口,用于實現(xiàn)多線程,與實現(xiàn)Runnable類似,但是功能更強大,通過實現(xiàn)Callable接口,我們需要重寫call()方法,該方法可以在任務(wù)結(jié)束后提供一個返回值,需要的朋友可以參考下
    2023-08-08
  • Windows下如何安裝配置Redis環(huán)境

    Windows下如何安裝配置Redis環(huán)境

    這篇文章主要介紹了Windows下如何安裝配置Redis環(huán)境,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-12-12

最新評論

集安市| 安乡县| 伊吾县| 册亨县| 永安市| 浮梁县| 吉隆县| 泰兴市| 林西县| 罗甸县| 漳浦县| 洛浦县| 锡林郭勒盟| 青浦区| 临桂县| 屯门区| 吉林省| 白银市| 濉溪县| 东丰县| 长岛县| 威信县| 沧源| 奉化市| 田林县| 府谷县| 兴山县| 荔波县| 谢通门县| 施秉县| 德钦县| 安新县| 历史| 正蓝旗| 江阴市| 宣城市| 天等县| 济宁市| 田阳县| 南昌县| 涞水县|