Spring需要三個(gè)級(jí)別緩存解決循環(huán)依賴(lài)原理解析
導(dǎo)學(xué)
Spring的三級(jí)緩存是繞不過(guò)去的一個(gè)坎兒。面試也經(jīng)常被問(wèn)到。而網(wǎng)文大多都在講Spring三級(jí)緩存的用途,而分析的很好的很少。
接下來(lái)整篇文章分析下:Spring為什么要使用三級(jí)緩存解決循環(huán)依賴(lài),而不是二級(jí)緩存或是一級(jí)緩存
先來(lái)明白一下Spring實(shí)例化一個(gè)Bean的過(guò)程中幾個(gè)重要概念
getBean
通過(guò)getBean方法獲取單例Bean,每個(gè)bean的創(chuàng)建都是從該方法開(kāi)始的。
getSingleton
Spring中最最重要的核心邏輯,沒(méi)有之一。該方法依次從一級(jí)緩存、二級(jí)緩存、三級(jí)緩存中獲取單例bean對(duì)象。
注意:如果從三級(jí)緩存中獲取到對(duì)象之后,就會(huì)被立即移動(dòng)到二級(jí)緩存。下面是源碼
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
三個(gè)級(jí)別緩存作用
一級(jí)緩存:存放最終的單例Bean,里面所有的Bean都是直接能使用的。(這個(gè)大家一定都明白就不多說(shuō)了)
三級(jí)緩存:存放一個(gè)工廠對(duì)象,這個(gè)工廠對(duì)象有一個(gè)getObject方法。工廠一般由lambda表達(dá)式組成,在工廠中主要完成了對(duì)Aop的代理。執(zhí)行一些Bean的擴(kuò)展邏輯。
二級(jí)緩存:沒(méi)錯(cuò),先介紹三級(jí)緩存是有目的的。二級(jí)緩存只有在getSingleton(前文提到)方法中,才會(huì)把三級(jí)緩存獲得的對(duì)象存入二級(jí)緩存。并且刪除三級(jí)緩存中的工廠對(duì)象。至于為什么總結(jié)里會(huì)說(shuō)。
循環(huán)依賴(lài)示例
廢話不多說(shuō),先給兩個(gè)類(lèi)Aoo和Boo,這兩個(gè)類(lèi)形成循環(huán)依賴(lài),代碼簡(jiǎn)單如下。
@Component
public class Aoo {
@Autowired
private Boo boo;
}
@Component
public class Boo {
@Autowired
private Aoo aoo;
}
循環(huán)依賴(lài)執(zhí)行流程
首先Spring會(huì)掃描指定的包,把所有標(biāo)注@Component注解的類(lèi)順序的實(shí)例化。Spring啟動(dòng)時(shí),容器中沒(méi)有任何bean(當(dāng)然是有一些內(nèi)部bean的,但是這樣說(shuō)便于我們理解)
下面來(lái)理解下Spring實(shí)例化Bean的順序,
- 啟動(dòng)時(shí)
- 檢測(cè)到Aoo開(kāi)始實(shí)例化Aoo對(duì)象
- 調(diào)用doGetBean方法實(shí)例化Aoo
- 調(diào)用getSingleton方法,試圖從三級(jí)緩存中依次獲取bean。但是第一次肯定都為空
Object sharedInstance = getSingleton(beanName);
- 因?yàn)榫彺鏋榭?,所以程序繼續(xù)往下走
sharedInstance = getSingleton(beanName, () -> {
return createBean(beanName, mbd, args);
});
程序調(diào)用getSingleton方法創(chuàng)建Aoo對(duì)象,并且該方法傳入一個(gè)lambda表達(dá)式,這個(gè)表達(dá)式后面是會(huì)被放入三級(jí)緩存的。
- 我們來(lái)看下getSingleton方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
return singletonObject;
}
可以看到該方法中試圖通過(guò)第二個(gè)參數(shù),也就是上一步的lambda表達(dá)式創(chuàng)建Aoo對(duì)象,并且創(chuàng)建完成后把Aoo對(duì)象存入一級(jí)緩存。 此時(shí)一級(jí)緩存何時(shí)加入的我們清楚了。
- 接下來(lái)我們來(lái)看下這個(gè)lambda表達(dá)式,也即是
createBean方法。而它又調(diào)用了doCreateBean方法。
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
// 省略無(wú)關(guān)代碼
return doCreateBean(beanName, mbdToUse, args);
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
// 創(chuàng)建一個(gè)Aoo的包裝對(duì)象。此時(shí)Aoo是一個(gè)空殼對(duì)象其中的所有屬性均為空
BeanWrapper instanceWrapper = null;
Object bean = instanceWrapper.getWrappedInstance();
// 判斷是否提前暴露(其實(shí)就是循環(huán)依賴(lài)了)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 把Aoo對(duì)象加入三級(jí)緩存!?。?
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
// 給Aoo對(duì)象注入屬性
populateBean(beanName, mbd, instanceWrapper);
// 初始化Aoo(一些擴(kuò)展點(diǎn),與循環(huán)依賴(lài)關(guān)系不大)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
if (earlySingletonExposure) {
// 如果提前暴露對(duì)象,則嘗試從緩存中獲取。
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
exposedObject = earlySingletonReference;
}
}
return exposedObject;
}
第一步:創(chuàng)建一個(gè)Aoo的空殼對(duì)象,此時(shí)Aoo其中的屬性還沒(méi)有值。
第二步:把工廠存入三級(jí)緩存,緩存的key就是對(duì)象的名稱(chēng)aoo,而value是一個(gè)工廠對(duì)象的lambda表達(dá)式,這個(gè)工廠對(duì)象會(huì)返回Aoo對(duì)象。這個(gè)工廠會(huì)執(zhí)行getEarlyBeanReference方法,該方法中會(huì)完成AOP動(dòng)態(tài)代理。需要重點(diǎn)說(shuō)明一下,從三級(jí)緩存中取出的對(duì)象每次都是不一樣的。因?yàn)樗敲看未砩傻摹?/strong>
第三步:執(zhí)行populateBean方法。為Aoo注入屬性,Aoo只有一個(gè)屬性Boo。Spring在注入Boo屬性的時(shí)候發(fā)現(xiàn)容器沒(méi)有Boo對(duì)象。
第四步:從這一步就循環(huán)到文章的最開(kāi)始了,接下來(lái)我用小寫(xiě)數(shù)字表示的步驟表示上文提到的步驟?;氐?code>3調(diào)用doGetBean方法實(shí)例化Boo。
第五步:執(zhí)行4調(diào)用getSingleton方法,試圖從三級(jí)緩存中依次獲取bean。前面說(shuō)過(guò)第一次肯定都為空,這次是第一次獲取Boo對(duì)象肯定也還是空的。
第六步:依次執(zhí)行5,6,7,在7中會(huì)把Boo對(duì)象存入三級(jí)緩存中。 沒(méi)錯(cuò),任何一個(gè)Bean都會(huì)先放到三級(jí)緩存中。此時(shí)三級(jí)緩存中有兩個(gè)Bean了,分別是Aoo和Boo
第七步:在7中的populateBean方法開(kāi)始給Boo注入屬性了,Boo只有一個(gè)屬性Aoo,Spring在注入Aoo屬性是會(huì)從容器中獲取,也就是調(diào)用getBean方法,此時(shí)發(fā)現(xiàn)三級(jí)緩存中有Aoo。就會(huì)從三級(jí)緩存中獲取Aoo對(duì)象并給Boo的這個(gè)屬性賦值。同時(shí)也會(huì)把Aoo對(duì)象從三級(jí)緩存移動(dòng)到二級(jí)緩存中。此時(shí)一級(jí)緩存為空、二級(jí)緩存中有Aoo對(duì)象、三級(jí)緩存中有Boo對(duì)象。此時(shí)Aoo、Boo的狀態(tài)還都是創(chuàng)建的過(guò)程中。
第八步:Boo的屬性已經(jīng)完成,回到上面的6它會(huì)把Boo對(duì)象添加到一級(jí)緩存,并從三級(jí)緩存中移除(這兒沒(méi)二級(jí)緩存啥事兒嘿嘿嘿)。 此時(shí)一級(jí)緩存中有一個(gè)對(duì)象Boo、二級(jí)緩存中有Aoo對(duì)象、三級(jí)緩存為空。
第九步:既然Boo都已經(jīng)在一級(jí)緩存當(dāng)中了,那么接著第三步來(lái)說(shuō),此時(shí)Aoo的屬性Boo也完成了賦值。此時(shí)Aoo也是一個(gè)完整對(duì)象了。但它此刻還在二級(jí)緩存當(dāng)中。
第十步:在7執(zhí)行完畢之后,回歸到6的代碼,執(zhí)行addSingleton方法把Aoo從二級(jí)緩存移動(dòng)到一級(jí)緩存當(dāng)中。至此,依賴(lài)注入完畢。一級(jí)緩存中有Aoo對(duì)象和Boo對(duì)象。二級(jí)、三級(jí)緩存為空。
思考:為什么需要三個(gè)級(jí)別的緩存來(lái)解決循環(huán)依賴(lài)
現(xiàn)在來(lái)思考一下為什么一定要是三個(gè)級(jí)別的緩存呢?我們來(lái)刪除二級(jí)緩存后看這個(gè)問(wèn)題。下面我們就使用只有一級(jí)緩存和三級(jí)緩存這2個(gè)緩存來(lái)看下循環(huán)依賴(lài)的問(wèn)題能不能解決。
還是Aoo和Boo兩個(gè)類(lèi)循環(huán)依賴(lài)
- Spring啟動(dòng)
- 從一級(jí)緩存和三級(jí)緩存中獲取Aoo,緩存中沒(méi)有,則創(chuàng)建
- 創(chuàng)建Aoo的空殼對(duì)象,并把它和工廠對(duì)象放入三級(jí)緩存中。
- 對(duì)Aoo進(jìn)行屬性注入,發(fā)現(xiàn)Boo即不在一級(jí)緩存,也不在三級(jí)緩存。只能創(chuàng)建了
- 創(chuàng)建Boo對(duì)象
- 對(duì)Boo進(jìn)行屬性注入,發(fā)現(xiàn)三級(jí)緩存中有Aoo對(duì)象,直接從三級(jí)緩存中獲取。
- Boo對(duì)象屬性裝配完成,把它從三級(jí)緩存移到一級(jí)緩存。
- Aoo對(duì)象屬性裝配完成,此時(shí)從三級(jí)緩存中移到一級(jí)緩存。
乍一看沒(méi)啥問(wèn)題是不是,其實(shí)不是的。問(wèn)題出在第6步,和第8步。通過(guò)前面的講解,一定要了解到,三級(jí)緩存中每次返回的對(duì)象都不一樣。所以第6步和第8步如果都從三級(jí)緩存中獲取Aoo對(duì)象, 這兩步中的Aoo對(duì)象不是同一個(gè),Spring中的Aoo對(duì)象和Boo對(duì)象就會(huì)使這個(gè)樣子

總結(jié)
首先不是說(shuō)非要三級(jí)緩存機(jī)制才能解決循環(huán)依賴(lài),一級(jí)緩存同樣可以解決,把三級(jí)緩存代碼平鋪化就好了嘛,或者使用JVM指令,字節(jié)碼等技術(shù)完成循環(huán)依賴(lài),但你想一下,那樣的話代碼的可讀性必然很低。所以第一個(gè)原因就是使用三級(jí)緩存解決循環(huán)依賴(lài)使得代碼可讀性非常好。
第二個(gè)原因是三級(jí)緩存中的工廠,每次getObject方法返回的實(shí)例不是同一個(gè)對(duì)象,所以需要二級(jí)緩存來(lái)緩存一下三級(jí)緩存生成的bean,這樣就保證了兩個(gè)類(lèi)的屬性是環(huán)形依賴(lài),不會(huì)破壞循環(huán)依賴(lài)。
以上就是Spring需要三個(gè)級(jí)別緩存解決循環(huán)依賴(lài)原理解析的詳細(xì)內(nèi)容,更多關(guān)于Spring三個(gè)級(jí)別緩存循環(huán)依賴(lài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java基于IDEA實(shí)現(xiàn)http編程的示例代碼
這篇文章主要介紹了Java基于IDEA實(shí)現(xiàn)http編程的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Java中使用SQLite數(shù)據(jù)庫(kù)的實(shí)現(xiàn)示例
SQLite是一種嵌入式數(shù)據(jù)庫(kù)引擎,可以在各種平臺(tái)上使用,本文主要介紹了Java中使用SQLite數(shù)據(jù)庫(kù)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
java實(shí)現(xiàn)超市庫(kù)存管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)超市庫(kù)存管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
詳解如何配置Spring Batch批處理失敗重試機(jī)制
這篇文章主要來(lái)和大家一起探討一下如何在Spring批處理框架中配置重試邏輯,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-06-06
Java連接SAP RFC實(shí)現(xiàn)數(shù)據(jù)抽取的示例詳解
這篇文章主要為大家學(xué)習(xí)介紹了Java如何連接SAP RFC實(shí)現(xiàn)數(shù)據(jù)抽取的功能,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,需要的可以了解下2023-08-08
Java后端學(xué)習(xí)精華之TCP通信傳輸協(xié)議詳解
TCP/IP是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,它會(huì)保證數(shù)據(jù)不丟包、不亂序。TCP全名是Transmission Control Protocol,它是位于網(wǎng)絡(luò)OSI模型中的第四層2021-09-09

