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

Spring詳細講解循環(huán)依賴是什么

 更新時間:2022年08月15日 15:12:48   作者:l昨日青天  
這篇文章主要介紹了Java中的Spring循環(huán)依賴詳情,文章基于Java的相關(guān)資料展開詳細介紹,具有一定的參考價值,需要的小伙伴可以參考一下

前言

Spring在我們實際開發(fā)過程中真的太重要了,當(dāng)你在公司做架構(gòu)升級、沉淀工具等都會多多少少用到Spring,本人也一樣,在研習(xí)了好幾遍Spring源碼之后,產(chǎn)生一系列的問題, 也從網(wǎng)上翻閱了各種資料,最近說服了自己,覺得還是得整理一下,有興趣的朋友可以一起討論溝通一波。回到標(biāo)題,我們要知道以下幾點:

(1)什么是循環(huán)依賴?

(2)Spring如何解決循環(huán)依賴

(3)只用一級緩存會存在什么問題

(4)只用二級緩存會存在什么問題

(5)Spring 為什么不用二級緩存來解決循環(huán)依賴問題

什么是循環(huán)依賴

很直接的一張圖:

循環(huán)依賴分為三種:構(gòu)造器注入方式的循環(huán)依賴、setter注入方式的循環(huán)、屬性注入方式的循環(huán)依賴;

其中構(gòu)造器注入方式造成的循環(huán)依賴Spring無法解決,這一點可以通過調(diào)試Spring源碼得到結(jié)論。

(ps:X和Y都是構(gòu)造器注入彼此,嚴(yán)謹(jǐn)一點)

Spring如何處理的循環(huán)依賴

Spring通過三級緩存解決了循環(huán)依賴,其中一級緩存為單例池(singletonObjects),二級緩存為早期曝光對象earlySingletonObjects,三級緩存為早期曝光對象工廠(singletonFactories)。

當(dāng)A、B兩個類發(fā)生循環(huán)引用時,在A完成實例化后,就使用實例化后的對象去創(chuàng)建一個對象工廠,并添加到三級緩存中,如果A被AOP代理,那么通過這個工廠獲取到的就是A代理后的對象,如果A沒有被AOP代理,那么這個工廠獲取到的就是A實例化的對象。當(dāng)A進行屬性注入時,會去創(chuàng)建B,同時B又依賴了A,所以創(chuàng)建B的同時又會去調(diào)用getBean(a)來獲取需要的依賴,此時的getBean(a)會從緩存中獲取,第一步,先獲取到三級緩存中的工廠;第二步,調(diào)用對象工工廠的getObject方法來獲取到對應(yīng)的對象,得到這個對象后將其注入到B中。緊接著B會走完它的生命周期流程,包括初始化、后置處理器等。當(dāng)B創(chuàng)建完后,會將B再注入到A中,此時A再完成它的整個生命周期。至此,循環(huán)依賴結(jié)束,上源碼:

第一次創(chuàng)建A時

截圖處代碼不成立,直接返回,進行A的創(chuàng)建過程

真正觸發(fā)去創(chuàng)建A的地方

這里將A加入正在創(chuàng)建的一個集合,代表A正在創(chuàng)建當(dāng)中

暴露早期A的半成品對象,如果A被AOP代理,那就是暴露A的代理對象,將改對象的創(chuàng)建工廠添加至三級緩存中,那么什么會去用呢?

進行屬性注入,在這里發(fā)現(xiàn)需要注入B,然后進行B的創(chuàng)建

B的創(chuàng)建同A一樣,此時就不截圖了。

然后在進行B的屬性注入時,發(fā)現(xiàn)需要注入A,再次回到A的創(chuàng)建,回到第一次創(chuàng)建的那個地方:

從三級緩存中獲取A的半成品對象,添加至二級緩存中。

此時將這個半成品對象給到B,讓B完成初始化,然后再次回到A的創(chuàng)建過程,上圖中觸發(fā)createBean的那個地方:

生成最終的A對象,放入一級緩存中。

只用一級緩存會存在什么問題

只使用一級緩存,也就是將所有的 bean 的實例都放在同一個 Map 容器中。其中就包括已經(jīng)初始化好的 bean 和未初始化好的 bean。

已經(jīng)初始化好的 bean: 指經(jīng)過了 bean 創(chuàng)建的三個階段之后的 bean 對象

未初始化好的 bean : 指經(jīng)過了 bean 創(chuàng)建的第一個階段,只將 bean 實例創(chuàng)建出來了

bean 的創(chuàng)建過程分三個階段:

1、創(chuàng)建實例 createBeanInstance

2、填充依賴 populateBean

3、initializeBean

假設(shè) bean 是需要 AOP 增強的,那么最終放到緩存中的應(yīng)該是一個代理 bean。而代理 bean 的產(chǎn)生是在 initializeBean(第三階段) 的時候。所以,我們推導(dǎo)出:如果只使用一級緩存的話,緩存的插入應(yīng)該放在 initializeBean 之后。

如果在 initializeBean 的時候記錄緩存,那么碰到循環(huán)依賴的情況,需要在 populateBean(第二階段) 的時候再去注入循環(huán)依賴的 bean,此時,緩存中是沒有循環(huán)依賴的 bean 的,就會導(dǎo)致 bean 重新創(chuàng)建實例。

這樣顯然是不行的。

反例:循環(huán)依賴場景:A–>B–>A

A 在 createBeanInstance 時,創(chuàng)建了 bean 實例,接著 populateBean 會填充依賴的 bean B,從而觸發(fā) B 的加載;

B 在 populateBean 時,發(fā)現(xiàn)要注入依賴的 bean A,先從緩存中獲取 bean A,獲取不到,就會重新創(chuàng)建 bean A。

這樣違背了 bean 的單例性,所以只使用一級緩存是不行的。

理論上使用一級緩存是可以解決普通場景下的循環(huán)依賴的,因為對于普通場景,從始至終 bean 的對象引用始終都是不變的。

但是,如果被循環(huán)依賴的 bean 是一個 AOP 增強的代理 bean 的話,bean 的原始引用和最終產(chǎn)生的 AOP 增強 bean 的引用是不一樣的,一級緩存就搞不定了。

疑問:如果在 createBeanInstance 之后就生成代理對象放入一級緩存呢?

我們或許會有疑問,如果不按 spring 原本的設(shè)計,我們在 bean 創(chuàng)建的第一步createBeanInstance 之后就判斷是否生成代理對象,并將要暴露的對象放入一級緩存是不是就可以解決所有場景的循環(huán)依賴問題呢?

分析:

再利用上面的反例來分析一遍,放入一級緩存的 bean 與暴露到容器中的 bean (不管是否有代理)始終是同一個 bean,看似好像是沒有問題的,好像是可以解決循環(huán)依賴的問題。

但是這里忽略了一個問題:bean 創(chuàng)建的第二步中 populateBean 的底層實現(xiàn)是將原始 bean 對象包裝成 BeanWrapper,然后通過 BeanWrapper 利用反射設(shè)置值的。

如果在 populateBean 之前生成的是一個代理對象的話,就帶來了另外一個問題 :jdk proxy 產(chǎn)生的代理對象是實現(xiàn)的目標(biāo)類的接口,jdk proxy 的代理類通過 BeanWrapper 去利用反射設(shè)置值時會因為找不到相應(yīng)的屬性或者方法而報錯。

所以,如果在 createBeanInstance 之后就生成代理對象放入一級緩存,也是行不通的。

只用二級緩存會存在什么問題

使用二級緩存也可以分為兩種:使用singletonObjects和earlySingletonObjects,或者使用singletonObjects和singletonFactories。

①使用singletonObjects和earlySingletonObjects

成品放在singletonObjects中,半成品放在earlySingletonObjects中

流程可以這樣走:實例化A ->將半成品的A放入earlySingletonObjects中 ->填充A的屬性時發(fā)現(xiàn)取不到B->實例化B->將半成品的B放入earlySingletonObjects中->從earlySingletonObjects中取出A填充B的屬性->將成品B放入singletonObjects,并從earlySingletonObjects中刪除B->將B填充到A的屬性中->將成品A放入singletonObjects并刪除earlySingletonObjects。

這樣的流程是線程安全的,不過如果A上加個切面(AOP),這種做法就沒法滿足需求了,因為earlySingletonObjects中存放的都是原始對象,而我們需要注入的其實是A的代理對象。

②使用singletonObjects和singletonFactories

成品放在singletonObjects中,半成品通過singletonFactories來獲取

流程是這樣的:實例化A ->創(chuàng)建A的對象工廠并放入singletonFactories中 ->填充A的屬性時發(fā)現(xiàn)取不到B->實例化B->創(chuàng)建B的對象工廠并放入singletonFactories中->從singletonFactories中獲取A的對象工廠并獲取A填充到B中->將成品B放入singletonObjects,并從singletonFactories中刪除B的對象工廠->將B填充到A的屬性中->將成品A放入singletonObjects并刪除A的對象工廠。

同樣,這樣的流程也適用于普通的IOC已經(jīng)有并發(fā)的場景,但如果A上加個切面(AOP)的話,這種情況也無法滿足需求,因為當(dāng)A和多個對象發(fā)生循環(huán)依賴時,其他對象拿到的都是A的不同的代理對象。

疑問:如果在createBeanInstance之后就生成代理對象放入二級緩存呢?

我們思考一種簡單的情況,就以單獨創(chuàng)建A為例,假設(shè)AB之間現(xiàn)在沒有依賴關(guān)系,但是A被代理了,這個時候當(dāng)A完成實例化后還是會進入下面這段代碼:

    // A是單例的,mbd.isSingleton()條件滿足
    // allowCircularReferences:這個變量代表是否允許循環(huán)依賴,默認是開啟的,條件也滿足
    // isSingletonCurrentlyInCreation:正在在創(chuàng)建A,也滿足
    boolean earlySingletonExposure = (mbd.isSingleton() &&this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 所以earlySingletonExposure=true
    // 還是會進入到這段代碼中
    if(earlySingletonExposure) {
    // 還是會通過三級緩存提前暴露一個工廠對象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

看到了吧,即使沒有循環(huán)依賴,也會將其添加到三級緩存中,而且是不得不添加到三級緩存中,因為到目前為止Spring也不能確定這個Bean有沒有跟別的Bean出現(xiàn)循環(huán)依賴。

假設(shè)我們在這里直接使用二級緩存的話,那么意味著所有的Bean在這一步都要完成AOP代理。這樣做有必要嗎?

不僅沒有必要,而且違背了Spring在結(jié)合AOP跟Bean的生命周期的設(shè)計!Spring結(jié)合AOP跟Bean的生命周期本身就是通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器來完成的,在這個后置處理的postProcessAfterInitialization方法中對初始化后的Bean完成AOP代理。如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給Bean先創(chuàng)建代理,但是沒有出現(xiàn)循環(huán)依賴的情況下,設(shè)計之初就是讓Bean在生命周期的最后一步完成代理而不是在實例化后就立馬完成代理。

Spring 為什么不用二級緩存來解決循環(huá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)依賴屬于一個特殊的場景,如果在第 3 步 initializeBean 時才去生成 AOP 代理 bean 的話,那么在第 2 步 populateBean 注入循環(huán)依賴 bean 時就拿不到 AOP 代理 bean 進行注入。

所以,循環(huán)依賴打破了 AOP 代理 bean 生成的時機,需要在 populateBean 之前就生成 AOP 代理 bean。

而且,生成 AOP 代理需要執(zhí)行 BeanPostProcessor,而 Spring 原本的設(shè)計是在第 3 步 initializeBean 時才去調(diào)用 BeanPostProcessor 的。

并不是每個 bean 都需要進行這樣的處理,所以, Spring 沒有直接在 createBeanInstance 之后直接生成 bean 的早期引用,而是將 bean 的原始對象包裝成了一個 ObjectFactory 放到了三級緩存 Map<String, Object> earlySingletonObjects。

當(dāng)需要用到 bean 的早期引用的時候,才通過三級緩存 Map<String, ObjectFactory<?>> singletonFactories 來進行獲取。

如果只使用二級緩存來解決循環(huán)依賴的話,那么每個 bean 的創(chuàng)建流程中都需要插入一個流程——創(chuàng)建 bean 的早期引用放入二級緩存。

其實,在真實的開發(fā)中,絕大部分的情況下都不涉及到循環(huán)依賴,而且 createBeanInstance --> populateBean --> initializeBean 這個流程也更加符合常理。

所以,猜想 Spring 不用二級緩存來解決循環(huán)依賴問題,是為了保證處理時清晰明了,bean 的創(chuàng)建就是三個階段: createBeanInstance --> populateBean --> initializeBean

只有碰到 AOP 代理 bean 被循環(huán)依賴時的場景,才去特殊處理,提前生成 AOP 代理 bean。

總結(jié)

如果沒有循環(huán)依賴的情況的話,一級緩存就可以搞定所有的情況,只需要在 bean 完全初始化好之后將其放入一級緩存即可。

但是一級緩存解決不了循環(huán)依賴的情況,所以,Spring 使用三級緩存來解決了循環(huán)依賴問題。如果使用二級緩存的話,理論上是可行的,但是 Spring 選擇了三級緩存來實現(xiàn),讓 bean 的創(chuàng)建流程更加符合常理,更加清晰明了。

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

相關(guān)文章

  • 使用java執(zhí)行定時任務(wù)示例

    使用java執(zhí)行定時任務(wù)示例

    這篇文章主要介紹了使用java執(zhí)行定時任務(wù)示例,需要的朋友可以參考下
    2014-04-04
  • Java關(guān)于重排鏈表詳細解析

    Java關(guān)于重排鏈表詳細解析

    在我們平時的代碼過程中,鏈表是我們經(jīng)常遇到的一個數(shù)據(jù)結(jié)構(gòu),面試題中鏈表占很大一部分,可見鏈表操作是非常重要的。本篇文章我們來探究一下如何進行重排鏈表
    2022-01-01
  • RabbitMQ消息單獨與批量的TTL詳細介紹

    RabbitMQ消息單獨與批量的TTL詳細介紹

    這篇文章主要介紹了RabbitMQ消息單獨與批量的TTL,TTL全名是Time To Live存活時間,表示當(dāng)消息由生產(chǎn)端存入MQ當(dāng)中的存活時間,當(dāng)時間到達的時候還未被消息就會被自動清除,感興趣的同學(xué)可以參考下文
    2023-05-05
  • Java8新特性之StampedLock_動力節(jié)點Java學(xué)院整理

    Java8新特性之StampedLock_動力節(jié)點Java學(xué)院整理

    本文從synchronized、Lock到Java8新增的StampedLock進行對比分析,對Java8新特性之StampedLock相關(guān)知識感興趣的朋友一起看看吧
    2017-06-06
  • java Servlet 實現(xiàn)動態(tài)驗證碼圖片示例

    java Servlet 實現(xiàn)動態(tài)驗證碼圖片示例

    這篇文章主要介紹了java Servlet 實現(xiàn)動態(tài)驗證碼圖片示例的資料,這里整理了詳細的代碼,有需要的小伙伴可以參考下。
    2017-02-02
  • Spring MVC接受表單自動封裝特性實例解析

    Spring MVC接受表單自動封裝特性實例解析

    這篇文章主要介紹了Spring MVC接受表單自動封裝特性實例解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • java如何生成可變表頭的excel

    java如何生成可變表頭的excel

    這篇文章主要為大家詳細介紹了java生成可變表頭excel的方法,傳入一個表頭和數(shù)據(jù),將數(shù)據(jù)導(dǎo)入到excel中,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • SpringCloud-Alibaba-Nacos啟動失敗解決方案

    SpringCloud-Alibaba-Nacos啟動失敗解決方案

    這篇文章主要介紹了SpringCloud-Alibaba-Nacos啟動失敗解決方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • Java京東面試題之為什么HashMap線程不安全

    Java京東面試題之為什么HashMap線程不安全

    那天,小二去京東面試,面試官老王一上來就甩給了他一道面試題:為什么 HashMap 是線程不安全的?這個問題哪能難的住小二,這篇文章詳細解答該題目
    2021-11-11
  • MyBatis-Plus動態(tài)返回實體類示例詳解

    MyBatis-Plus動態(tài)返回實體類示例詳解

    這篇文章主要為大家介紹了MyBatis-Plus動態(tài)返回實體類示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07

最新評論

古交市| 始兴县| 左贡县| 崇仁县| 册亨县| 本溪市| 昆山市| 博客| 庆云县| 弥勒县| 印江| 甘德县| 甘德县| 榆树市| 昌吉市| 娄底市| 东阿县| 沐川县| 舟山市| 温宿县| 霞浦县| 正宁县| 宜丰县| 盐边县| 尖扎县| 瓦房店市| 五峰| 惠安县| 诏安县| 新平| 肥西县| 武平县| 莱阳市| 仲巴县| 永善县| 卓尼县| 凤翔县| 临武县| 福泉市| 和平县| 东阳市|