![7d76c324-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOCAYql-AAE89eiPpBg241.png)
1. 基礎知識
1.1 什么是循環依賴 ?
一個或多個對象之間存在直接或間接的依賴關系,這種依賴關系構成一個環形調用,有下面 3 種方式。
![7d9ea39e-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOCAWgX-AACJg3wTuVo674.png)
我們看一個簡單的 Demo,對標“情況 2”。
@Service
publicclassLouzai1{
@Autowired
privateLouzai2louzai2;
publicvoidtest1(){
}
}
@Service
publicclassLouzai2{
@Autowired
privateLouzai1louzai1;
publicvoidtest2(){
}
}
這是一個經典的循環依賴,它能正常運行,后面我們會通過源碼的角度,解讀整體的執行流程。
1.2 三級緩存
解讀源碼流程之前,spring 內部的三級緩存邏輯必須了解,要不然后面看代碼會蒙圈。
- 第一級緩存 :singletonObjects,用于保存實例化、注入、初始化完成的 bean 實例;
- 第二級緩存 :earlySingletonObjects,用于保存實例化完成的 bean 實例;
- 第三級緩存 :singletonFactories,用于保存 bean 創建工廠,以便后面有機會創建代理對象。
這是最核心,我們直接上源碼:
![7db71186-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOCATMONAAGU2i1BM7I330.png)
執行邏輯:
- 先從“第一級緩存”找對象,有就返回,沒有就找“二級緩存”;
- 找“二級緩存”,有就返回,沒有就找“三級緩存”;
- 找“三級緩存”,找到了,就獲取對象,放到“二級緩存”,從“三級緩存”移除。
1.3 原理執行流程
我把“情況 2”執行的流程分解為下面 3 步,是不是和“套娃”很像 ?
![7de0ad48-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOCALdtAAAMjSLwN_lg095.png)
整個執行邏輯如下:
- 在第一層中,先去獲取 A 的 Bean,發現沒有就準備去創建一個,然后將 A 的代理工廠放入“三級緩存”(這個 A 其實是一個半成品,還沒有對里面的屬性進行注入 ),但是 A 依賴 B 的創建,就必須先去創建 B;
- 在第二層中,準備創建 B,發現 B 又依賴 A,需要先去創建 A;
- 在第三層中,去創建 A,因為第一層已經創建了 A 的代理工廠,直接從“三級緩存”中拿到 A 的代理工廠,獲取 A 的代理對象,放入“二級緩存” ,并清除“三級緩存”;
- 回到第二層,現在有了 A 的代理對象,對 A 的依賴完美解決(這里的 A 仍然是個半成品 ),B 初始化成功;
- 回到第一層,現在 B 初始化成功,完成 A 對象的屬性注入,然后再填充 A 的其它屬性,以及 A 的其它步驟(包括 AOP),完成對 A 完整的初始化功能(這里的 A 才是完整的 Bean )。
- 將 A 放入“一級緩存”。
為什么要用 3 級緩存 ?我們先看源碼執行流程,后面我會給出答案。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
2. 源碼解讀
注意:Spring 的版本是 5.2.15.RELEASE ,否則和我的代碼不一樣!!!
上面的知識,網上其實都有,下面才是我們的重頭戲,讓你跟著樓仔,走一遍代碼流程。
2.1 代碼入口
![7e0d204e-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGAas0UAAH6cclwtPM040.png)
![7e326a16-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGAAhhyAAHkTXwrG0s640.png)
這里需要多跑幾次,把前面的 beanName 跳過去,只看 louzai1。
![7e50616a-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGARFUQAAKzjDeBtYw149.png)
![7e72d556-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGAPMWnAABKIDOueOg231.png)
2.2 第一層
![7e905a36-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGAYLCwAAFjB5BuQfY863.png)
進入 doGetBean(),從 getSingleton() 沒有找到對象,進入創建 Bean 的邏輯。
![7eb0d770-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGAeZeJAADikfgwECg975.png)
![7ecdbbb0-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOGAbJuNAAH7LsXUnrc731.png)
進入 doCreateBean() 后,調用 addSingletonFactory()。
![7ef65eb2-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKAEoKBAAJwg3kNWBU043.png)
往三級緩存 singletonFactories 塞入 louzai1 的工廠對象。
![7f17fe3c-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKAI4nUAAJjy-itUuI826.png)
![7f52c4a4-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKAWHAEAAGX6jg4XSU338.png)
進入到 populateBean(),執行 postProcessProperties(),這里是一個策略模式,找到下圖的策略對象。
![7f71d3a8-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKASLvMAASGNz8uJkQ772.png)
正式進入該策略對應的方法。
![7f9b11b4-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKAEuxwAAF6J3JK2TA052.png)
下面都是為了獲取 louzai1 的成員對象,然后進行注入。
![7fa65650-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKALHC6AADCdYetwO0138.png)
![7fd14914-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAT_aCAAD5MnKXpwA922.png)
![7fed7fd0-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAM29qAAEfZlKu2bs750.png)
![7ff82c0a-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAGYuxAAFzcqWHaLA095.png)
進入 doResolveDependency(),找到 louzai1 依賴的對象名 louzai2
![8008ff76-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAHP7CAADJbMahxYo239.png)
需要獲取 louzai2 的 bean,是 AbstractBeanFactory 的方法。
![8018f020-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAJkEfAAFN7n2mUMI987.png)
正式獲取 louzai2 的 bean。
![8034f6b2-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAIGVdAABOBGdXcZ0724.png)
到這里,第一層套娃基本結束,因為 louzai1 依賴 louzai2,下面我們進入第二層套娃。
2.3 第二層
![804c28dc-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOOAaBhuAADI3isDWM4751.png)
獲取 louzai2 的 bean,從 doGetBean(),到 doResolveDependency(),和第一層的邏輯完全一樣,找到 louzai2 依賴的對象名 louzai1。
前面的流程全部省略,直接到 doResolveDependency()。
![806a87fa-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAWNwcAAJqj2n3IbQ523.png)
正式獲取 louzai1 的 bean。
![80776cea-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAVGWUAAHcSu98ma4690.png)
到這里,第二層套娃結束,因為 louzai2 依賴 louzai1,所以我們進入第三層套娃。
2.4 第三層
![809c6b8a-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAB4InAADyqJ4310Q944.png)
獲取 louzai1 的 bean,在第一層和第二層中,我們每次都會從 getSingleton() 獲取對象,但是由于之前沒有初始化 louzai1 和 louzai2 的三級緩存,所以獲取對象為空。
![80c2d7c0-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAayL6AAD_OIqQN7w800.png)
![80e2b93c-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAZWJBAABPTf-NXRo875.png)
敲重點!敲重點!!敲重點!!!
到了第三層,由于第三級緩存有 louzai1 數據,這里使用三級緩存中的工廠,為 louzai1 創建一個代理對象,塞入二級緩存。
![80fca392-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAD_IWAAOTiTnaTm4194.png)
這里就拿到了 louzai1 的代理對象,解決了 louzai2 的依賴關系,返回到第二層。
2.5 返回第二層
返回第二層后,louzai2 初始化結束,這里就結束了么?二級緩存的數據,啥時候會給到一級呢?
甭著急,看這里,還記得在 doGetBean() 中,我們會通過 createBean() 創建一個 louzai2 的 bean,當 louzai2 的 bean 創建成功后,我們會執行 getSingleton(),它會對 louzai2 的結果進行處理。
![812f996e-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOSAcySSAACqDM5CGec843.png)
我們進入 getSingleton(),會看到下面這個方法。
![813aad22-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAYVqTAAFDBx5iEQM196.png)
這里就是處理 louzai2 的 一、二級緩存的邏輯,將二級緩存清除,放入一級緩存。
![815da99e-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAYAApAANPViMR7Wk195.png)
2.6 返回第一層
同 2.5,louzai1 初始化完畢后,會把 louzai1 的二級緩存清除,將對象放入一級緩存。
![818a0aac-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAcXoYAANiL89VQok212.png)
到這里,所有的流程結束,我們返回 louzai1 對象。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
3. 原理深度解讀
3.1 什么要有 3 級緩存 ?
這是一道非常經典的面試題,前面已經告訴大家詳細的執行流程,包括源碼解讀,但是沒有告訴大家為什么要用 3 級緩存?
這里是重點!敲黑板!!!
我們先說“一級緩存”的作用,變量命名為 singletonObjects,結構是 Map
“二級緩存”先放放,我們直接看“三級緩存”的作用,變量命名為 singletonFactories,結構是 Map
那這個對象的代理工廠有什么作用呢,我先給出答案,它的主要作用是存放半成品的單例 Bean,目的是為了“打破循環” ,可能大家還是不太懂,這里我再稍微解釋一下。
我們回到文章開頭的例子,創建 A 對象時,會把實例化的 A 對象存入“三級緩存”,這個 A 其實是個半成品,因為沒有完成 A 的依賴屬性 B 的注入,所以后面當初始化 B 時,B 又要去找 A,這時就需要從“三級緩存”中拿到這個半成品的 A(這里描述,其實也不完全準確,因為不是直接拿,為了讓大家好理解,我就先這樣描述),打破循環。
那我再問一個問題,為什么“三級緩存”不直接存半成品的 A,而是要存一個代理工廠呢 ?答案是因為 AOP。
在解釋這個問題前,我們看一下這個代理工廠的源碼,讓大家有一個更清晰的認識。
直接找到創建 A 對象時,把實例化的 A 對象存入“三級緩存”的代碼,直接用前面的兩幅截圖。
![7ef65eb2-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKAEoKBAAJwg3kNWBU043.png)
![7f17fe3c-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOKAI4nUAAJjy-itUuI826.png)
下面我們主要看這個對象工廠是如何得到的,進入 getEarlyBeanReference() 方法。
![81e4a674-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAALpzAACmKnrctgs794.png)
![8200c28c-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAcOmaAAEnjghR-BA194.png)
![8212d3fa-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAJeDbAABu6w7SLXc034.png)
![822e74d4-2f1f-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/53/wKgZomTnHOWAa0hNAAG5wFdR9Pw465.png)
最后一幅圖太重要了,我們知道這個對象工廠的作用:
- 如果 A 有 AOP,就創建一個代理對象;
- 如果 A 沒有 AOP,就返回原對象。
那“二級緩存”的作用就清楚了,就是用來存放對象工廠生成的對象,這個對象可能是原對象,也可能是個代理對象。
我再問一個問題,為什么要這樣設計呢?把二級緩存干掉不行么 ?我們繼續往下看。
3.2 能干掉第 2 級緩存么 ?
@Service
publicclassA{
@Autowired
privateBb;
@Autowired
privateCc;
publicvoidtest1(){
}
}
@Service
publicclassB{
@Autowired
privateAa;
publicvoidtest2(){
}
}
@Service
publicclassC{
@Autowired
privateAa;
publicvoidtest3(){
}
}
根據上面的套娃邏輯,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。
假如 A 需要進行 AOP ,因為代理對象每次都是生成不同的對象,如果干掉第二級緩存,只有第一、三級緩存:
- B 找到 A 時,直接通過三級緩存的工廠的代理對象,生成對象 A1。
- C 找到 A 時,直接通過三級緩存的工廠的代理對象,生成對象 A2。
看到問題沒?你通過 A 的工廠的代理對象,生成了兩個不同的對象 A1 和 A2 ,所以為了避免這種問題的出現,我們搞個二級緩存,把 A1 存下來,下次再獲取時,直接從二級緩存獲取,無需再生成新的代理對象。
所以“二級緩存”的目的是為了避免因為 AOP 創建多個對象,其中存儲的是半成品的 AOP 的單例 bean。
如果沒有 AOP 的話,我們其實只要 1、3 級緩存,就可以滿足要求。
4. 寫在最后
我們再回顧一下 3 級緩存的作用:
- 一級緩存:為“Spring 的單例屬性”而生 ,就是個單例池,用來存放已經初始化完成的單例 Bean;
- 二級緩存:為“解決 AOP”而生 ,存放的是半成品的 AOP 的單例 Bean;
- 三級緩存:為“打破循環”而生 ,存放的是生成半成品單例 Bean 的工廠方法。
如果你能理解上面我說的三條,恭喜你,你對 Spring 的循環依賴理解得非常透徹!
關于循環依賴的知識,其實還有,因為篇幅原因,我就不再寫了,這篇文章的重點,一方面是告訴大家循環依賴的核心原理,另一方面是讓大家自己去 debug 代碼 ,跑跑流程,挺有意思的。
可能有同學會問 “樓哥,你之前是不是經常看源碼,然后這個流程,你是不是 debug 了很久?”
我之前其實沒怎么看過開源代碼,這個流程,前期理論知識看了 2.5 個小時,然后 debug 4.5 小時,就基本全部走通了,最難的地方,就是三層套娃,稍微有些繞。
這里也簡單說一下我看源碼的心得:
- 需要掌握基本的設計模式;
- 看源碼前,最好能找一些理論知識先看看;
- 學會讀英文注釋,不會的話就百度翻譯;
- debug 時,要克制自己,不要陷入無用的細節 ,這個最重要。
其中最難的是第 4 步,因為很多同學看 Spring 源碼,每看一個方法,就想多研究研究,這樣很容易被繞進去了,這個要學會克制,有大局觀,并能分辨哪里是核心邏輯 ,至于如何分辨,可以在網上先找些資料,如果沒有的話,就只能多看代碼了。
今天的源碼解析就到這了~
審核編輯 :李倩
-
源碼
+關注
關注
8文章
653瀏覽量
29460 -
spring
+關注
關注
0文章
340瀏覽量
14393
原文標題:痛快!SpringBoot終于幫我們禁止了Spring循環依賴!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
火語言如何循環讀取表格
![火語言如何<b class='flag-5'>循環</b>讀取表格](https://file1.elecfans.com//web3/M00/07/68/wKgZPGelsiWAWhNMAAGt31nR4Rk322.png)
可靠性溫度循環試驗至少需要幾個循環?
![可靠性溫度<b class='flag-5'>循環</b>試驗至少需要幾個<b class='flag-5'>循環</b>?](https://file1.elecfans.com/web3/M00/06/FF/wKgZPGeR7xCAPRD3AAAjAT9_Eho395.png)
深入理解C語言:循環語句的應用與優化技巧
![深入理解C語言:<b class='flag-5'>循環</b>語句的應用與優化技巧](https://file1.elecfans.com/web2/M00/FC/CD/wKgZomaWI5uASgBaAABuQHdMO4I302.png)
掃描速率和濃度對循環伏安圖有什么影響
rnn是遞歸神經網絡還是循環神經網絡
循環神經網絡算法原理及特點
循環神經網絡的缺點是存在什么問題
循環神經網絡的基本概念
什么是RNN (循環神經網絡)?
![什么是RNN (<b class='flag-5'>循環</b>神經網絡)?](https://file1.elecfans.com/web2/M00/C2/1B/wKgZomXgKxOACsTWAAAJbSjoWF0873.jpg)
評論