天天看點

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

  吃完晚飯,坐在院子裡和父親聊天

  父親:你有什麼人生追求?

  我:金錢和美女

  父親對着我的頭就是一丁弓,說道:小小年紀,怎麼這麼庸俗,重說一次

  我:事業與愛情

  父親贊賞的摸了我的頭,說道:嗯嗯,這就對咯

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

  做 Java 開發的,一般都繞不開 Spring,那麼面試中肯定會被問到 Spring 的相關内容,而循環依賴又是 Spring 中的高頻面試題

  這不前段時間,我的一朋友去面試,就被問到了循環依賴,結果他還在上面還小磕了一下,他們聊天過程如下

  面試官:說下什麼是循環依賴

  朋友: 兩個或則兩個以上的對象互相依賴對方,最終形成 閉環 。例如 A 對象依賴 B 對象,B 對象也依賴 A 對象

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

  面試官:那會有什麼問題呢

  朋友:對象的建立過程會産生死循環,類似如下

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

  面試官:Spring 是如何解決的呢

  朋友:通過三級緩存提前暴露對象來解決的

  面試官:三級緩存裡面分别存的什麼

  朋友:一級緩存裡存的是成品對象,執行個體化和初始化都完成了,我們的應用中使用的對象就是一級緩存中的

    二級緩存中存的是半成品,用來解決對象建立過程中的循環依賴問題

    三級緩存中存的是 ObjectFactory<?> 類型的 lambda 表達式,用于處理存在 AOP 時的循環依賴問題

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

  面試官:為什麼要用三級緩存來解決循環依賴問題(隻用一級緩存行不行,隻用二級緩存行不行)

  朋友:霸點蠻,隻用一級緩存也是可以解決的,但是會複雜化整個邏輯

    半成品對象是沒法直接使用的(存在 NPE 問題),是以 Spring 需要保證在啟動的過程中,所有中間産生的半成品對象最終都會變成成品對象

    如果将半成品對象和成品對象都混在一級緩存中,那麼為了區分他們,勢必會增加一些而外的标記和邏輯處理,這就會導緻對象的建立過程變得複雜化了

    将半成品對象與成品對象分開存放,兩級緩存各司其職,能夠簡化對象的建立過程,更簡單、直覺

    如果 Spring 不引入 AOP,那麼兩級緩存就夠了,但是作為 Spring 的核心之一,AOP 怎能少得了呢

    是以為了處理 AOP 時的循環依賴,Spring 引入第三級緩存來處理循環依賴時的代理對象的建立

  面試官:如果将代理對象的建立過程提前,緊随于執行個體化之後,而在初始化之前,那是不是就可以隻用兩級緩存了?

  朋友心想:這到了我知識盲區了呀,我幹哦! 卻點頭道:你說的有道理耶,我沒有細想這一點,回頭我去改改源碼試試看

  前面幾問,感覺朋友答的還不錯,但是最後一問中的第三級緩存的作用,回答的還差那麼一丢丢,到底那一丢丢是什麼,我們慢慢往下看

  正式開講之前,我們先來回顧一些内容,不然可能後面的内容看起來有點蒙(其實主要是怕你們杠我)

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    一般而言,對象的建立分成兩步:執行個體化、初始化,執行個體化指的是從堆中申請記憶體空間,完成 JVM 層面的對象建立,初始化指的是給屬性值指派

    當然也可以直接通過構造方法一步完成執行個體化與初始化,實作對象的建立

    當然還要其他的方式,比如工廠等

    有三種:構造方法注入、setter 方法注入、接口注入

    接口注入的方式太靈活,易用性比較差,是以并未廣泛應用起來,大家知道有這麼一說就好,不要去細扣了

    構造方法注入的方式,将執行個體化與初始化并在一起完成,能夠快速建立一個可直接使用的對象,但它沒法處理循環依賴的問題,了解就好

    setter 方法注入的方式,是在對象執行個體化完成之後,再通過反射調用對象的 setter 方法完成屬性的指派,能夠處理循環依賴的問題,是後文的基石,必須要熟悉

    三級緩存的順序是由查詢循序而來,與在類中的定義順序無關

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    是以第一級緩存: singletonObjects ,第二級緩存: earlySingletonObjects ,第三級緩存: singletonFactories 

    抛開 Spring,讓我們自己來實作,會如何處理循環依賴問題呢

    半成品雖然不能直接在應用中使用,但是在對象的建立過程中還是可以使用的嘛,就像這樣

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    有入棧,有出棧,而不是一直入棧,也就解決了循環依賴的死循環問題

    Spring 是不是也是這樣實作的了,基于 5.2.12.RELEASE ,我們一起來看看 Spring 是如何解決循環依賴的

  下面會從幾種不同的情況來進行源碼跟蹤,如果中途有疑問,先用筆記下來,全部看完了之後還有疑問,那就請評論區留言

    代碼非常簡單:spring-no-dependence

    此時, SimpleBean 對象在 Spring 中是如何建立的呢,我們一起來跟下源碼

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    接下來,我們從 DefaultListableBeanFactory 的 preInstantiateSingletons 方法開始 debug 

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    沒有跟進去的方法,或者快速跳過的,我們可以先略過,重點關注跟進去了的方法和停留了的代碼,此時有幾個屬性值中的内容值得我們留意下

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    我們接着從 createBean 往下跟

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    關鍵代碼在 doCreateBean 中,其中有幾個關鍵方法的調用值得大家去跟下

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    此時:代理對象的建立是在對象執行個體化完成,并且初始化也完成之後進行的,是對一個成品對象建立代理對象

    是以此種情況下:隻用一級緩存就夠了,其他兩個緩存可以不要

    代碼依舊非常簡單:spring-circle-simple,此時循環依賴的兩個類是: Circle 和 Loop 

    對象的建立過程與前面的基本一緻,隻是多了循環依賴,少了 AOP,是以我們重點關注: populateBean 和 initializeBean 方法

    先建立的是 Circle 對象,那麼我們就從建立它的 populateBean 開始,再開始之前,我們先看看三級緩存中的資料情況

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎
Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    我們開始跟 populateBean ,它完成屬性的填充,與循環依賴有關,一定要仔細看,仔細跟

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    對 circle 對象的屬性 loop 進行填充的時候,去 Spring 容器中找 loop 對象,發現沒有則進行建立,又來到了熟悉的 createBean 

    此時三級緩存中的資料沒有變化,但是 Set<String> singletonsCurrentlyInCreation 中多了個 loop 

    相信到這裡大家都沒有問題,我們繼續往下看

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

     loop 執行個體化完成之後,對其屬性 circle 進行填充,去 Spring 中擷取 circle 對象,又來到了熟悉的 doGetBean 

    此時一、二級緩存中都沒有 circle、loop ,而三級緩存中有這兩個,我們接着往下看,重點來了,仔細看哦

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    通過 getSingleton 擷取 circle 時,三級緩存調用了 getEarlyBeanReference ,但由于沒有 AOP,是以 getEarlyBeanReference 直接傳回了普通的 半成品 circle 

    然後将 半成品 circle 放到了二級緩存,并将其傳回,然後填充到了 loop 對象中

    此時的 loop 對象就是一個成品對象了;接着将 loop 對象傳回,填充到 circle 對象中,如下如所示

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    我們發現直接将 成品 loop 放到了一級緩存中,二級緩存自始至終都沒有過 loop ,三級緩存雖說存了 loop ,但沒用到就直接 remove 了

    此時緩存中的資料,相信大家都能想到了

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    雖說 loop 對象已經填充到了 circle 對象中,但還有一丢丢流程沒走完,我們接着往下看

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    将 成品 circle 放到了一級緩存中,二級緩存中的 circle 沒有用到就直接 remove 了,最後各級緩存中的資料相信大家都清楚了,就不展示了

    我們回顧下這種情況下各級緩存的存在感,一級緩存存在感十足,二級緩存可以說無存在感,三級緩存有存在感(向 loop 中填充 circle 的時候有用到)

    是以此種情況下:可以減少某個緩存,隻需要兩級緩存就夠了

    代碼還是非常簡單:spring-circle-aop,在循環依賴的基礎上加了 AOP

    比上一種情況多了 AOP,我們來看看對象的建立過程有什麼不一樣;同樣是先建立 Circle ,在建立 Loop 

    建立過程與上一種情況大體一樣,隻是有小部分差別,跟源碼的時候我會在這些差別上有所停頓,其他的會跳過,大家要仔細看

    執行個體化 Circle ,然後填充 半成品 circle 的屬性 loop ,去 Spring 容器中擷取 loop 對象,發現沒有

    則執行個體化 Loop ,接着填充 半成品 loop 的屬性 circle ,去 Spring 容器中擷取 circle 對象

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    這個過程與前一種情況是一緻的,就直接跳過了,我們從上圖中的紅色步驟開始跟源碼,此時三級緩存中的資料如下

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    注意看啦,重要的地方來了

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    我們發現從第三級緩存擷取 circle 的時候,調用了 getEarlyBeanReference 建立了 半成品 circle 的代理對象

    将 半成品 circle 的代理對象放到了第二級緩存中,并将代理對象傳回指派給了 半成品 loop 的 circle 屬性 

    注意:此時是在進行 loop 的初始化,但卻把 半成品 circle 的代理對象提前建立出來了

     loop 的初始化還未完成,我們接着往下看,又是一個重點,仔細看

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    在 initializeBean 方法中完成了 半成品 loop 的初始化,并在最後建立了 loop 成品 的代理對象

     loop 代理對象建立完成之後會将其放入到第一級緩存中(移除第三級緩存中的 loop ,第二級緩存自始至終都沒有 loop )

    然後将 loop 代理對象傳回并指派給 半成品 circle 的屬性 loop ,接着進行 半成品 circle 的 initializeBean 

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    因為 circle 的代理對象已經生成過了(在第二級緩存中),是以不用再生成代理對象了;将第二級緩存中的 circle 代理對象移到第一級緩存中,并傳回該代理對象

    此時各級緩存中的資料情況如下(普通 circle 、 loop 對象在各自代理對象的 target 中)

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    我們回顧下這種情況下各級緩存的存在感,一級緩存仍是存在感十足,二級緩存有存在感,三級緩存挺有存在感

      第三級緩存提前建立 circle 代理對象,不提前建立則隻能給 loop 對象的屬性 circle 指派成 半成品 circle ,那麼 loop 對象中的 circle 對象就無 AOP 增強功能了

      第二級緩存用于存放 circle 代理,用于解決循環依賴;也許在這個示例展現的不夠明顯,因為依賴比較簡單,依賴稍複雜一些,就能感受到了

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

      第一級緩存存放的是對外暴露的對象,可能是代理對象,也可能是普通對象

    是以此種情況下:三級緩存一個都不能少

    沒有依賴,有AOP 這種情況中,我們知道 AOP 代理對象的生成是在成品對象建立完成之後建立的,這也是 Spring 的設計原則,代理對象盡量推遲建立

    循環依賴 + AOP 這種情況中, circle 代理對象的生成提前了,因為必須要保證其 AOP 功能,但 loop 代理對象的生成還是遵循的 Spring 的原則

    如果我們打破這個原則,将代理對象的建立邏輯提前,那是不是就可以不用三級緩存了,而隻用兩級緩存了呢?

    代碼依舊簡單:spring-circle-custom,隻是對 Spring 的源碼做了非常小的改動,改動如下

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    去除了第三級緩存,并将代理對象的建立邏輯提前,置于執行個體化之後,初始化之前;我們來看下執行結果

Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎

    并沒有什麼問題,有興趣的可以去跟下源碼,跟蹤過程相信大家已經掌握,這裡就不再示範了

    目前基于 xml 的配置越來越少,而基于注解的配置越來越多,是以了也提供了一個注解的版本供大家去跟源碼

    代碼還是很簡單:spring-circle-annotation

    跟蹤流程與 循環依賴 + AOP 那種情況基本一緻,隻是屬性的填充有了一些差別,具體可檢視:Spring 的自動裝配 → 騷話 @Autowired 的底層工作原理

  1、三級緩存各自的作用

    第一級緩存存的是對外暴露的對象,也就是我們應用需要用到的

    第二級緩存的作用是為了處理循環依賴的對象建立問題,裡面存的是半成品對象或半成品對象的代理對象

    第三級緩存的作用處理存在 AOP + 循環依賴的對象建立問題,能将代理對象提前建立

  2、Spring 為什麼要引入第三級緩存

    嚴格來講,第三級緩存并非缺它不可,因為可以提前建立代理對象

    提前建立代理對象隻是會節省那麼一丢丢記憶體空間,并不會帶來性能上的提升,但是會破環 Spring 的設計原則

    Spring 的設計原則是盡可能保證普通對象建立完成之後,再生成其 AOP 代理(盡可能延遲代理對象的生成)

    是以 Spring 用了第三級緩存,既維持了設計原則,又處理了循環依賴;犧牲那麼一丢丢記憶體空間是願意接受的