- Java開發,總會遇到問三級快取的。
- 看了很多文章,感覺不是很透徹。打算自己寫一個自以為很詳細的對三級快取的理解。 有圖文。也有文字概括。受不了動圖的可以看文字概括哦
進入正題:
- 在開發過程中會遇到迴圈依賴的問題。 就跟下圖一樣
- Spring在為此設計了三級快取來解決以上依賴的問題
首先我們得知道 三級快取裡面分別存的什麼
一級快取裡存的是成品物件,例項化和初始化都完成了,我們的應用中使用的物件就是一級快取中的
二級快取中存的是半成品,用來解決物件建立過程中的迴圈依賴問題
三級快取中存的是 ObjectFactory<?> 型別的 lambda 表示式,用於處理存在 AOP 時的迴圈依賴問題
Spring 三級快取的順序
三級快取的順序是由查詢循序而來,與在類中的定義順序無關
所以第一級快取:singletonObjects ,第二級快取:earlySingletonObjects ,第三級快取:singletonFactories
Spring 的的注入方式有三種:構造方法注入、setter 方法注入、介面注入
介面注入的方式太靈活,易用性比較差,所以並未廣泛應用起來,大家知道有這麼一說就好,不要去細扣了
構造方法注入的方式,將例項化與初始化並在一起完成,能夠快速建立一個可直接使用的物件,但它沒法處理迴圈依賴的問題,瞭解就好
setter 方法注入的方式,是在物件例項化完成之後,再通過反射呼叫物件的 setter 方法完成屬性的賦值,能夠處理迴圈依賴的問題,是後文的基石,必須要熟悉
Spring 原始碼分析
- 下面會從幾種不同的情況來進行原始碼跟蹤
1、 沒有依賴,有 AOP
程式碼非常簡單: spring-no-dependence
-
上圖的邏輯就是
ClassPathXmlApplicationContext#refresh
-
->
this.finishBeanFactoryInitialization(beanFactory)
-
->
beanFactory.preInstantiateSingletons()
-
->
isFactoryBean
判斷bean, 然後呼叫 getBean 方法。 -
接下來呼叫
doGetBean
方法
- 上圖邏輯是
DefaultSingletonBeanRegistry#getSingleton
判斷是否存在快取當中,如果沒有則進行建立Bean - 可以觀察到
Map<String, Object> singletonObjects 無 simpleBean
Map<String, Object> earlySingletonObjects 無 simpleBean
Map<String, ObjectFactory<?>> singletonFactories 無 simpleBean
Set<String> singletonsCurrentlyInCreation 有 simpleBean
- 說明bean在建立過成中
- 我們接著從 createBean 往下跟
- 關鍵程式碼在
doCreateBean
中,其中有幾個關鍵方法的呼叫值得大家去跟下
Map<String, Object> singletonObjects 儲存的 simpleBean 的代理物件
Map<String, Object> earlySingletonObjects 由始至終都沒有 simpleBean 物件
Map<String, ObjectFactory<?>> singletonFactories 存入了一會資料立馬就刪除了 並未使用過
// 以下說的方法均在 AbstractAutowireCapableBeanFactory 類下
// createBeanInstance 通過反射完成物件的例項化,獲得半成品物件。 給分配記憶體空間, 即使半成品
// populateBean 填充半成品屬性, 如果有依賴物件則在這裡引入
// initializeBean 初始化半成品物件
// applyBeanPostProcessorsAfterInitialization BeanPostProcessor的後置處理,AOP 的代理物件替換就是在這裡完成的
- 此時:代理物件的建立是在物件例項化完成,並且初始化也完成之後進行的,是對一個成品物件建立代理物件
- 所以
《沒有依賴,有 AOP》
情況下:只用一級快取就夠了,其他兩個快取可以不要也能完成物件
2、迴圈依賴,沒有AOP
程式碼依舊非常簡單: spring-circle-simple 此時迴圈依賴的兩個類是: Circle
和 Loop
物件的建立過程與前面的基本一致,只是多了迴圈依賴,少了 AOP,所以我們重點關注: populateBean
和 initializeBean
方法
先建立的是 Circle
物件,那麼我們就從建立它的 populateBean
開始,再開始之前,我們先看看三級快取中的資料情況
Map<String, Object> singletonObjects 無 circle 也無 loop 物件
Map<String, Object> earlySingletonObjects 無 circle 也無 loop 物件
Map<String, ObjectFactory<?>> singletonFactories 只有 cicle 的 lambda
Set<String> singletonsCurrentlyInCreation 只有 circle
- 我們開始跟
populateBean
,它完成屬性的填充,與迴圈依賴有關,一定要仔細看,仔細跟
對 circle
物件的屬性 loop
進行填充的時候,去 Spring 容器中找 loop
物件,發現沒有則進行建立,又來到了熟悉的 createBean
此時三級快取中的資料沒有變化,但是 Set<String> singletonsCurrentlyInCreation
中多了個 loop
標識loop
正在建立中
loop
例項化完成之後,對其屬性 circle
進行填充,去 Spring 中獲取 circle
物件,又來到了熟悉的 doGetBean
此時一、二級快取 (singletonObjects``earlySingletonObjects
) 中都沒有 circle
、loop
,而三級快取中有這兩個
通過 getSingleton
獲取circle
時,三級快取呼叫了 getEarlyBeanReference
,但由於沒有 AOP,所以 getEarlyBeanReference
直接返回了普通的 半成品 circle
然後將 半成品 circle
放到了二級快取,並將其返回,然後填充到了 loop
物件中
此時的 loop
物件就是一個成品物件了;接著將 loop
物件返回,填充到 circle
物件中,如下如所示
我們發現直接將 成品 loop
放到了一級快取中,二級快取自始至終都沒有過 loop
,三級快取雖說存了 loop
,但沒用到就直接 remove 了
此時快取中的資料,相信大家都能想到了
Map<String, Object> singletonObjects 無 circle 有 loop 物件
Map<String, Object> earlySingletonObjects 有 circle 無 loop 物件
Map<String, ObjectFactory<?>> singletonFactories 無 circle 也無 loop
Set<String> singletonsCurrentlyInCreation 有 circle 無 loop 因為loop建立完畢了
- 當 loop 物件完成 建立bean的時候 會呼叫
DefaultSingletonBeanRegistry#getSingleton
->DefaultSingletonBeanRegistry#addSingleton
- 將資料物件移動到一級快取中。二級快取的 circle 沒用上就刪除了, 只有
circle
存在三級快取的資料被呼叫到了。將半成品的circle
給返回給 loop物件 - 所以
《迴圈依賴,沒有AOP》
情況下:可以減少某個快取,只需要兩級快取就夠了
概括:(迴圈依賴,沒有AOP)
- 上頭的步驟可概括為:
- 第一步。doCreateBean 進行
circle
的建立,建立步驟為: - circle 的流程:
- `AbstractBeanFactory#doGetBean` 獲取bean
- -> `AbstractAutowireCapableBeanFactory#createBean` 建立bean
- -> `AbstractAutowireCapableBeanFactory#doCreateBean` 開始建立bean
- -> `AbstractAutowireCapableBeanFactory#addSingletonFactory` 把bean的一個 lambda 到三級快取去了 singletonFactories
- -> `AbstractAutowireCapableBeanFactory#populateBean` 填充bean
- -> `AbstractAutowireCapableBeanFactory#applyPropertyValues` 檢查到有要新增的一來 進行填充
- -> `BeanDefinitionValueResolver#resolveValueIfNecessary` 注意 ! 這個位置獲取 loop 物件
```java
- 斷點 我們觀察下 三個快取 Map的儲存情況
```java
Map<String, Object> singletonObjects 無 circle 也無 loop 物件
Map<String, Object> earlySingletonObjects 無 circle 也無 loop 物件
Map<String, ObjectFactory<?>> singletonFactories 有 circle 也無 loop 物件
Set<String> singletonsCurrentlyInCreation 有 circle
- 第二步 然後 get
loop
Bean 會重複上面的步驟
- `AbstractBeanFactory#doGetBean` 獲取bean
- -> `AbstractAutowireCapableBeanFactory#createBean` 建立bean
- -> `AbstractAutowireCapableBeanFactory#doCreateBean` 開始建立bean
- -> `AbstractAutowireCapableBeanFactory#addSingletonFactory` 把bean的一個 lambda 到三級快取去了 singletonFactories
- -> `AbstractAutowireCapableBeanFactory#populateBean` 填充bean
- -> `AbstractAutowireCapableBeanFactory#applyPropertyValues` 檢查到有要新增的一來 進行填充
- -> `BeanDefinitionValueResolver#resolveValueIfNecessary` 注意 ! 這個位置改了。獲取的是 circle 物件
- 斷點 我們觀察下 三個快取 Map的儲存情況
Map<String, Object> singletonObjects 無 circle 也無 loop 物件
Map<String, Object> earlySingletonObjects 無 circle 也無 loop 物件
Map<String, ObjectFactory<?>> singletonFactories 有 circle 有 loop 物件
Set<String> singletonsCurrentlyInCreation 有 circle 有 loop 說明兩個物件都在建立中
- 關鍵點來了:
- 第三步 相當於程式是第二次進入
circle
的AbstractBeanFactory#doGetBean
- `AbstractBeanFactory#doGetBean` 第二次獲取 circle
- `AbstractBeanFactory#getSingleton(beanName)` 獲取 Bean 的快取
- `DefaultSingletonBeanRegistry#getSingleton(beanName, true)` 獲取 Bean 的快取
- `DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation(beanName)` 關鍵!! 判斷 circle 這個名字的bean是不是在建立過程
- `this.singletonFactories.get(beanName)` 獲取這個 circle 的 lambda 建立函式
- `singletonFactory.getObject()` 呼叫函式 獲取了一個半成品的物件。 也就是 loop 還為空的 circle物件
- `this.earlySingletonObjects.put(beanName, singletonObject)` 將物件加入到二級快取裡面去 earlySingletonObjects 增加了物件
// 附,只有 earlySingletonObjects 新增了一個 circle 物件,其他map 無改變。 並且loop的 singletonFactories 也未使用到
- 然後就返回了
circle
給到loop
進行屬性填充 - 完成
loop
建立 將loop
在 (earlySingletonObjects、singletonFactories、singletonsCurrentlyInCreation)清除。loop
新增物件到 singletonObjects - 返回建立好的
loop
給到circle
的填充屬性流程 - 填充完畢之後。在(earlySingletonObjects、singletonFactories、singletonsCurrentlyInCreation)清除。 新增
circle
物件到 singletonObjects - 注意 :
circle
就算只是半成品 那他也是在bean中是唯一的。 只要circle
的屬性在後面填充了loop
那麼在loop
的那個單例快取裡面。就會有迴圈依賴的circle
物件 - 其實在整個流程中 circle 會進入到二級快取當中。但是沒使用。就被remove了
- loop 在二級快取從來就沒有出現過。因為不會進入兩次 loop 的 doGetBean流程 。 loop的三級快取資料也沒使用過就被刪除了。
2、迴圈依賴,有AOP
程式碼還是非常簡單:spring-circle-aop ,在迴圈依賴的基礎上加了 AOP
比上一種情況多了 AOP,我們來看看物件的建立過程有什麼不一樣;同樣是先建立 Circle
,在建立Loop
建立過程與上一種情況大體一樣,只是有小部分割槽別,跟原始碼的時候我會在這些區別上有所停頓,其他的會跳過,大家要仔細看
例項化 Circle
,然後填充 半成品 circle
的屬性 loop
,去 Spring 容器中獲取 loop
物件,發現沒有
則例項化 Loop ,接著填充 半成品 loop
的屬性 circle
,去 Spring 容器中獲取 circle
物件
這個過程與前一種情況是一致的,就直接跳過了,此時三級快取中的資料如下:
Map<String, Object> singletonObjects 無 circle 也無 loop 物件
Map<String, Object> earlySingletonObjects 無 circle 也無 loop 物件
Map<String, ObjectFactory<?>> singletonFactories 有 circle 有 loop 物件
Set<String> singletonsCurrentlyInCreation 有 circle 有 loop 說明兩個物件都在建立中
我們發現從第三級快取獲取 circle
的時候,呼叫了 getEarlyBeanReference
建立了 半成品circle
的代理物件
將 半成品 circle
的代理物件放到了第二級快取中,並將代理物件返回賦值給了 半成品 loop
的 circle
屬性
注意:此時是在進行 loop
的初始化,但卻把 半成品 circle 的代理物件提前建立出來了
loop 的初始化還未完成,我們接著往下看,又是一個重點,仔細看
在 initializeBean
方法中完成了 半成品 loop
的初始化,並在最後建立了 loop
成品 的代理物件
loop
代理物件建立完成之後會將其放入到第一級快取中(移除第三級快取中的 loop
,第二級快取自始至終都沒有 loop
)
然後將 loop
代理物件返回並賦值給 半成品 circle
的屬性 loop
,接著進行 半成品 circle
的 initializeBean
因為 circle
的代理物件已經生成過了(在第二級快取中),所以不用再生成代理物件了;將第二級快取中的 circle 代理物件移到第一級快取中,並返回該代理物件
此時各級快取中的資料情況如下(普通circle
、 loop
物件在各自代理物件的 target
中)
Map<String, Object> singletonObjects 有 circle 代理物件 有 loop 代理物件
Map<String, Object> earlySingletonObjects 無 circle 無 loop 物件
Map<String, ObjectFactory<?>> singletonFactories 無 circle 無 loop 物件
Set<String> singletonsCurrentlyInCreation 無 circle 無 loop
我們回顧下這種情況下各級快取的存在感,一級快取仍是存在感十足,二級快取有存在感,三級快取挺有存在感
第三級快取提前建立 circle 代理物件,不提前建立則只能給 loop 物件的屬性 circle 賦值成 半成品 circle ,那麼 loop 物件中的 circle 物件就無 AOP 增強功能了
第二級快取用於存放 circle 代理,用於解決迴圈依賴;也許在這個示例體現的不夠明顯,因為依賴比較簡單,依賴稍複雜一些,就能感受到了
第一級快取存放的是對外暴露的物件,可能是代理物件,也可能是普通物件
所以此種情況下:三級快取一個都不能少
概括: (2、迴圈依賴,有AOP)
- 與
概括:(迴圈依賴,沒有AOP)
基本一致 - 在第三步發生變化:
- `AbstractBeanFactory#doGetBean` 第二次獲取 circle
- `AbstractBeanFactory#getSingleton(beanName)` 獲取 Bean 的快取
- `DefaultSingletonBeanRegistry#getSingleton(beanName, true)` 獲取 Bean 的快取
- `DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation(beanName)` 判斷 circle 這個名字的bean是不是在建立過程
- `this.singletonFactories.get(beanName)` 獲取這個 circle 的 lambda 建立函式
- `singletonFactory.getObject()` 呼叫函式 獲取了一個半成品的物件。(注意!! 有AOP環繞的物件在該位置會建立代理物件, 並且將代理物件 通過 AbstractAutoProxyCreator#getEarlyBeanReference 同步到AOP的建立類裡邊。為了後面的使用) 也就是 loop 還為空的 circle物件
- `this.earlySingletonObjects.put(beanName, singletonObject)` 將物件加入到二級快取裡面去 earlySingletonObjects 增加了物件
// 附,只有 earlySingletonObjects 新增了一個 circle 物件,其他map 無改變。
- 然後就完成了 loop 的建立。
- 然後進行完 circle 填充之後。
- -> `AbstractAutowireCapableBeanFactory#populateBean` 填充完bean之後
- -> `AbstractAutowireCapableBeanFactory#initializeBean` 進行 circle 的初始化
- -> `AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization` bean的後置通知。此位置會進行 bean AOP的環繞 返回代理物件
- 由於在上方 loop 獲取 circle 的時候不是已經建立了個代理物件了嗎。那麼這個aop就不能在新建一個代理類了。不然不一致
- 接著往下看
- -> `AbstractAutoProxyCreator#postProcessAfterInitialization` 建立代理物件
- -> `if (this.earlyProxyReferences.remove(cacheKey) != bean)` 這個時候 二級快取派上用場了。在這裡。判斷是否已經有代理類了。如果有代理類則不新建代理類物件。
// 這樣 circle 的代理就不會被重複建立了。 二級快取也派上了用場
4、迴圈依賴 + AOP + 刪除第三級快取
沒有依賴,有AOP 這種情況中,我們知道 AOP 代理物件的生成是在成品物件建立完成之後建立的,這也是 Spring 的設計原則,代理物件儘量推遲建立
迴圈依賴 + AOP 這種情況中, circle 代理物件的生成提前了,因為必須要保證其 AOP 功能,但 loop 代理物件的生成還是遵循的 Spring 的原則
如果我們打破這個原則,將代理物件的建立邏輯提前,那是不是就可以不用三級快取了,而只用兩級快取了呢?
程式碼依舊簡單:spring-circle-custom ,只是對 Spring 的原始碼做了非常小的改動,改動如下
去除了第三級快取,並將代理物件的建立邏輯提前,置於例項化之後,初始化之前;
總結
1、三級快取各自的作用
第一級快取存的是對外暴露的物件,也就是我們應用需要用到的
第二級快取的作用是為了處理迴圈依賴的物件建立問題,裡面存的是半成品物件或半成品物件的代理物件
第三級快取的作用處理存在 AOP + 迴圈依賴的物件建立問題,能將代理物件提前建立
2、Spring 為什麼要引入第三級快取
嚴格來講,第三級快取並非缺它不可,因為可以提前建立代理物件
提前建立代理物件只是會節省那麼一丟丟記憶體空間,並不會帶來效能上的提升,但是會破環 Spring 的設計原則
Spring 的設計原則是儘可能保證普通物件建立完成之後,再生成其 AOP 代理(儘可能延遲代理物件的生成)
所以 Spring 用了第三級快取,既維持了設計原則,又處理了迴圈依賴;犧牲那麼一丟丟記憶體空間是願意接受的