天天看點

手撕Spring源碼,徹底了解Spring循環依賴原理

強化是深度記憶的有效手段。簡單對上篇做個總結。Spring Bean建立的過程為:

手撕Spring源碼,徹底了解Spring循環依賴原理

1、根據class生成Bean定義

2、根據Bean進行執行個體化

3、将執行個體的Bean屬性進行填充

4、初始化Bean

5、Bean後置處理

6、完成Bean對象的建立

本系列的所有代碼文字在 https://github.com/xiexiaojing/yuna 裡可以找到。

引出問題

在上篇最後的代碼中,咱們來做這麼一件事,讓 testService 引用 userService :

手撕Spring源碼,徹底了解Spring循環依賴原理

加上原來的 userService 引用了 testService :

手撕Spring源碼,徹底了解Spring循環依賴原理

運作啟動類:

手撕Spring源碼,徹底了解Spring循環依賴原理

循環引用導緻棧溢出:

手撕Spring源碼,徹底了解Spring循環依賴原理

原因看代碼就很明白了,有遞歸調用導緻死循環依賴:

手撕Spring源碼,徹底了解Spring循環依賴原理

咱們模拟的就是Spring源碼,原理和Spring源碼是一樣的。那Spring最初也遇到了這個問題。它是怎麼解決的呢?

單例池二級緩存

Spring解決的思路也是很正常的思路:testService 引用了 userService 。要建立userService需要先擷取testService的Bean。那能不能先建立一個半成品先用着呢?這就是Spring解決循環依賴的二級緩存思想。

有二級緩存,那就有一級緩存。什麼是一級緩存呢?咱們頭兩節課裡介紹擷取Bean在Scope為單例時是從singletonObjects也就是單例池中擷取的。這個單例池作用是緩存單例對象,這就是一級緩存。

手撕Spring源碼,徹底了解Spring循環依賴原理

那咱們來看二級緩存,也叫半成品池。思想就是如果擷取Bean的時候發現Bean不存在,就從半成品池裡擷取。如果擷取不到則立即執行個體化一個放到半成品池裡。這樣,其他Bean建立依賴于它時,就可以直接先拿來用。確定依賴它的可以執行個體化成功。

定義半成品池:

手撕Spring源碼,徹底了解Spring循環依賴原理

擷取Bean時如果如果擷取不到則立即執行個體化一個放到半成品池裡:

手撕Spring源碼,徹底了解Spring循環依賴原理

總共加上了這四行代碼之後咱們再來運作一次:

手撕Spring源碼,徹底了解Spring循環依賴原理
手撕Spring源碼,徹底了解Spring循環依賴原理

單例池二級緩存存在的問題

記不記得咱們曾經手撕過mybatis的源碼?可以回顧一下《mybatis的本質和原理》這篇文章。想抽空寫一篇手撕spring整合mybatis的,大家有興趣的可以在評論區留言。本篇在看超15,就是預設想看了,8小時内我就把文章肝出來~

spring整合mybatis時,咱們使用時定義接口,并把掃描注解标注到接口上:

手撕Spring源碼,徹底了解Spring循環依賴原理

本質上Repository就是一個Component注解:

手撕Spring源碼,徹底了解Spring循環依賴原理

那問題來了,mybatis的操作sql語句的方法是接口不是類,不能被執行個體化呀,二級緩存提前執行個體化是有問題的。不信咱們來試驗一下:

手撕Spring源碼,徹底了解Spring循環依賴原理

注入一個接口,啟動時就報錯了:

手撕Spring源碼,徹底了解Spring循環依賴原理

因為本身啊,咱們利用Spring注入的Bean是代理類。比如mybatis的接口最終使用的是mybatis在執行完Bean後置處理器之後的代理類。

問題明确了:二級緩存由于提前執行個體化,執行個體化了一些不能被執行個體化的接口,會報錯。

單例池三級緩存

那如果你是Spring的設計者怎麼解決這個問題?那如果發現是接口就提前執行後置器處理器把代理類建立出來使用呗。别說,Spring的設計者還真也是這麼處理的。

首先定義一個接口,這個接口直接從Spring源碼裡拷貝一下:

手撕Spring源碼,徹底了解Spring循環依賴原理

這個接口的實作類作用就是實作getObject把接口真正替換成代理類。這裡舉得是mybatis的實作,具體可參考《mybatis的本質和原理》:

手撕Spring源碼,徹底了解Spring循環依賴原理

本質上Repository就是一個Component注解的别名。為了讓咱們在spring的注解能掃描到,咱們把它替換成@Component注解:

手撕Spring源碼,徹底了解Spring循環依賴原理

由于類所在的位置,咱們把掃描範圍擴大些:

手撕Spring源碼,徹底了解Spring循環依賴原理

定義三級緩存也叫工廠池:

手撕Spring源碼,徹底了解Spring循環依賴原理

如果是接口先執行個體化工廠池對象,從工廠池對象中擷取真正的Bean對象:

手撕Spring源碼,徹底了解Spring循環依賴原理

這裡隻是一個put操作,為了盡量和Spring源碼保持一緻,單獨拆成方法,加了下面判斷,其實核心就一行:

手撕Spring源碼,徹底了解Spring循環依賴原理

其他就是因為提前執行個體化了,在建立Bean時判斷如果已經執行個體化則不必再次執行個體化,隻是一個判斷:

手撕Spring源碼,徹底了解Spring循環依賴原理

改造就是這些,咱們來運作一下:

手撕Spring源碼,徹底了解Spring循環依賴原理
手撕Spring源碼,徹底了解Spring循環依賴原理

總結

本篇針對Spring建立時會遇到的兩個問題:死循環依賴問題和接口執行個體化問題給出了Spring的解決方案。

雖然代碼是我手撕的,和源碼不完全一緻,但原理是一樣的。面試的時候跟人家說Spring就是這麼實作的,一點問題也沒有。

在本系列的第一篇《手撕spring核心源碼,徹底搞懂spring流程》裡其實Spring IoC控制反轉和依賴注入原理就已經講完并實作了。這兩篇文章都是針對在此基礎上的需求和問題。《手撕Spring源碼(二),徹底了解Spring後置處理器》解決的是對Bean做增強的問題,本篇是随着Bean的多樣性引發的問題是怎麼解決的。

Spring本身就是如此:基于一個簡單的想法,再解決設計中遇到的問題。