- 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 用了第三級緩存,既維持了設計原則,又處理了循環依賴;犧牲那么一丟丟內存空間是願意接受的