關(guān)于SpringBoot的三級(jí)緩存的思考問(wèn)題小結(jié)
前言
在閱讀 Spring Boot 源碼的過(guò)程中,我對(duì) createBean 方法中那段廣為人知的邏輯——“三級(jí)緩存解決循環(huán)依賴”——產(chǎn)生了濃厚的興趣。我花了相當(dāng)多的時(shí)間去研究源碼,并參考了許多網(wǎng)上的解析與討論。
關(guān)于為什么要設(shè)計(jì)三級(jí)緩存,不同的說(shuō)法層出不窮,網(wǎng)上的博客質(zhì)量參差不齊:有人認(rèn)為這是出于性能優(yōu)化的考慮,有人認(rèn)為是為了支持 AOP 代理的提前暴露,也有人認(rèn)為兩級(jí)緩存無(wú)法完全應(yīng)對(duì)循環(huán)依賴的問(wèn)題。起初我也在這些觀點(diǎn)之間反復(fù)權(quán)衡,但隨著理解的深入,我找到了自己的答案。
我認(rèn)為,Spring Boot 之所以采用三級(jí)緩存的根本目的,并不在于性能或特定功能的支持,而在于在遵循 Bean 生命周期語(yǔ)義的前提下,允許在循環(huán)依賴的特殊場(chǎng)景中適度突破這一語(yǔ)義。
換句話說(shuō),三級(jí)緩存主要維護(hù)了 Spring 對(duì) Bean 創(chuàng)建過(guò)程的規(guī)范性。
一、SpringBoot具體解決了什么循環(huán)依賴?
Spring Boot中,Bean 之間的依賴可以通過(guò)多種方式注入,例如:
- 構(gòu)造函數(shù)注入(Constructor Injection)
- 字段注入(@Autowired)
- Setter 方法注入
- 接口回調(diào)注入(如 BeanFactoryAware、ApplicationContextAware 等)
但并不是所有注入方式都可能導(dǎo)致循環(huán)依賴,也不是所有循環(huán)依賴 Spring 都能“救回來(lái)”。三級(jí)緩存機(jī)制所能解決的,實(shí)際上只是**“單例 Bean 之間通過(guò)屬性注入(字段或 Setter)產(chǎn)生的循環(huán)依賴”**。
這里的構(gòu)造函數(shù)注入最為特殊,因?yàn)樗淖⑷霑r(shí)機(jī)是最早的,所以這里我將它們分為構(gòu)造函數(shù)注入和非構(gòu)造函數(shù)注入。
1.1 非構(gòu)造函數(shù)注入
@Component
@Data
public class CycleDependenceTestA {
@Autowired
public CycleDependenceTestB cycleDependenceTestB;
public CycleDependenceTestA() {}
}
@Component
@Data
public class CycleDependenceTestB {
@Autowired
public CycleDependenceTestA cycleDependenceTestA;
public CycleDependenceTestB() {}
}容器正常啟動(dòng),循環(huán)依賴問(wèn)題被解決。
1.2 構(gòu)造函數(shù)注入
構(gòu)造函數(shù)注入比較特殊,因?yàn)樗淖⑷霑r(shí)機(jī)是最早的。
@Component
@Data
public class CycleDependenceTestA {
public CycleDependenceTestB cycleDependenceTestB;
public CycleDependenceTestA(CycleDependenceTestB cycleDependenceTestB) {
this.cycleDependenceTestB = cycleDependenceTestB;
}
}
@Component
@Data
public class CycleDependenceTestB {
public CycleDependenceTestA cycleDependenceTestA;
public CycleDependenceTestB(CycleDependenceTestA cycleDependenceTestA) {
this.cycleDependenceTestA = cycleDependenceTestA;
}
}結(jié)果會(huì)報(bào)錯(cuò):
Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | cycleDependenceTestA defined in file [E:\Java space\codes\SpringBootDemo\target\classes\org\example\springbootdemo\beans\CycleDependenceTestA.class] ↑ ↓ | cycleDependenceTestB defined in file [E:\Java space\codes\SpringBootDemo\target\classes\org\example\springbootdemo\beans\CycleDependenceTestB.class] └─────┘ Action: Despite circular references being allowed, the dependency cycle between beans could not be broken. Update your application to remove the dependency cycle. Process finished with exit code 1
SpringBoot無(wú)法解決這種情況,原因很簡(jiǎn)單,往后看了解三層緩存原理后,自然會(huì)明白。
1.3 構(gòu)造函數(shù)和非構(gòu)造函數(shù)混合注入
這里的情況最為特殊和有趣,大家可以猜一猜,這種情況下的循環(huán)依賴的問(wèn)題能否解決呢?
我在這里告訴大家答案:50%概率可以解決,和注入的先后順序有關(guān)系。
1.3.1 不能解決的案例
@Component
@Data
public class CycleDependenceTestA {
public CycleDependenceTestB cycleDependenceTestB;
public CycleDependenceTestA(CycleDependenceTestB cycleDependenceTestB) {
this.cycleDependenceTestB = cycleDependenceTestB;
}
}
@Component
@Data
public class CycleDependenceTestB {
@Autowired
public CycleDependenceTestA cycleDependenceTestA;
public CycleDependenceTestB() {}
}結(jié)果是報(bào)了循環(huán)依賴的錯(cuò)誤的:
Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | cycleDependenceTestA defined in file [E:\Java space\codes\SpringBootDemo\target\classes\org\example\springbootdemo\beans\CycleDependenceTestA.class] ↑ ↓ | cycleDependenceTestB (field public org.example.springbootdemo.beans.CycleDependenceTestA org.example.springbootdemo.beans.CycleDependenceTestB.cycleDependenceTestA) └─────┘ Action: Despite circular references being allowed, the dependency cycle between beans could not be broken. Update your application to remove the dependency cycle. Process finished with exit code 1
1.3.2 可以解決的案例
@Component
@Data
public class CycleDependenceTestA {
@Autowired
public CycleDependenceTestB cycleDependenceTestB;
public CycleDependenceTestA() {}
}
@Component
@Data
public class CycleDependenceTestB {
public CycleDependenceTestA cycleDependenceTestA;
public CycleDependenceTestB(CycleDependenceTestA cycleDependenceTestA) {
this.cycleDependenceTestA = cycleDependenceTestA;
}
}容器正常啟動(dòng)
1.3.3 總結(jié)
上述兩個(gè)案例中,一個(gè)循環(huán)依賴被解決,另一個(gè)無(wú)法被解決,代碼區(qū)別是什么?其實(shí)就是執(zhí)行順序的問(wèn)題。至于為什么,先賣個(gè)關(guān)子,如果你看過(guò)底層源碼,了解Bean的生命周期,創(chuàng)建流程,自然會(huì)理解。請(qǐng)往下看。
二、如何解決的?
2.1 依賴注入時(shí)機(jī)
核心代碼在AbstractAutowireCapableBeanFactory的doCreateBean方法中。
// 核心邏輯:AbstractAutowireCapableBeanFactory#doCreateBean // 1. 創(chuàng)建 Bean 實(shí)例(構(gòu)造函數(shù)注入在此階段完成) instanceWrapper = createBeanInstance(beanName, mbd, args); // 2. 暴露早期引用,將用于生成代理的 ObjectFactory 放入第三級(jí)緩存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 3. 屬性依賴注入(此階段執(zhí)行 @Autowired、@Value 等注解邏輯) // 關(guān)鍵處理器:AutowiredAnnotationBeanPostProcessor populateBean(beanName, mbd, instanceWrapper); // 4. Bean 初始化(執(zhí)行初始化回調(diào)與 AOP 代理創(chuàng)建等邏輯) // 關(guān)鍵處理器:AbstractAutoProxyCreator 及其他 BeanPostProcessor exposedObject = initializeBean(beanName, exposedObject, mbd);
2.2 三層緩存
Spring 在解決循環(huán)依賴問(wèn)題時(shí),核心邏輯位于 DefaultSingletonBeanRegistry 類中。
2.2.1 三個(gè)重要的緩存容器
它們共同構(gòu)成了所謂的三級(jí)緩存機(jī)制:
// 一級(jí)緩存:存放完全初始化完成的單例 Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級(jí)緩存:存放提前暴露但尚未完全初始化的 Bean 實(shí)例 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三級(jí)緩存:存放可以生成 Bean 早期引用的工廠(通常是用于生成代理的 ObjectFactory) private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
2.2.2 核心方法:getSingleton()
// 源碼
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) {
if (!this.singletonLock.tryLock()) {
// Avoid early singleton inference outside of original creation thread.
return null;
}
try {
// 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();
// Singleton could have been added or removed in the meantime.
if (this.singletonFactories.remove(beanName) != null) {
this.earlySingletonObjects.put(beanName, singletonObject);
}
else {
singletonObject = this.singletonObjects.get(beanName);
}
}
}
}
}
finally {
this.singletonLock.unlock();
}
}
}
return singletonObject;
}核心邏輯如下:
- 先從一級(jí)緩存中找
Object singletonObject = this.singletonObjects.get(beanName);
- 一級(jí)緩存找不到,再?gòu)亩?jí)緩存找
singletonObject = this.earlySingletonObjects.get(beanName);
- 二級(jí)緩存也沒(méi)有,則從三級(jí)緩存取出工廠并生成早期引用
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.singletonFactories.remove(beanName); this.earlySingletonObjects.put(beanName, singletonObject); }
2.3 說(shuō)明
上面的代碼展示了 Spring 解決循環(huán)依賴的核心實(shí)現(xiàn)邏輯。為了更清晰地理解,我們可以先回到 Bean 的創(chuàng)建生命周期。
一個(gè) Bean 從創(chuàng)建到最終可用,大致會(huì)經(jīng)歷三個(gè)階段:
實(shí)例化 → 屬性注入 → 初始化。
只有當(dāng) Bean 完成初始化后,才能被認(rèn)為是一個(gè)“完整可用”的 Bean。
循環(huán)依賴問(wèn)題的關(guān)鍵在于:
當(dāng) Bean A 依賴 Bean B,而 Bean B 又依賴 Bean A 時(shí),如果嚴(yán)格按照生命周期順序執(zhí)行,那么雙方都會(huì)在“屬性注入階段”卡住——因?yàn)榇藭r(shí)彼此都還沒(méi)有完成創(chuàng)建。
Spring 的解決思路是:
在實(shí)例化完成但尚未初始化之前,提前暴露一個(gè)可以引用的 Bean 對(duì)象,供其他 Bean 使用。
換句話說(shuō),即使一個(gè) Bean 還沒(méi)完全準(zhǔn)備好(屬性未注入、后置處理器未執(zhí)行),Spring 也允許通過(guò)三級(jí)緩存機(jī)制,將它的“早期引用”暴露出去。這樣,另一個(gè) Bean 在注入時(shí)就能拿到一個(gè)有效的引用,從而打破循環(huán)依賴的僵局。
三、三級(jí)緩存到底解決了什么問(wèn)題
3.1 第三級(jí)緩存的特殊性
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
第三級(jí)緩存相較于前兩級(jí)緩存更為特殊——它保存的不是 Bean 實(shí)例本身,而是一個(gè) ObjectFactory 對(duì)象,也就是一個(gè)可執(zhí)行的回調(diào)函數(shù)。
要理解這種設(shè)計(jì)的意義,我們需要看看 Spring 在向第三級(jí)緩存注冊(cè)時(shí),究竟放入了什么邏輯:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// class AbstractAutowireCapableBeanFactory
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}Spring 會(huì)遍歷所有實(shí)現(xiàn)了 SmartInstantiationAwareBeanPostProcessor 接口的后置處理器,讓它們有機(jī)會(huì)提前介入 Bean 的引用創(chuàng)建過(guò)程。
其中最典型的后置處理器就是 AbstractAutoProxyCreator,它正是 Spring AOP 的底層核心之一。
// class AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyBeanReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
這里的 wrapIfNecessary() 方法會(huì)判斷當(dāng)前 Bean 是否需要被 AOP 切面增強(qiáng):
如果需要增強(qiáng),就在此時(shí)創(chuàng)建代理對(duì)象并返回;
如果不需要,則直接返回原始對(duì)象。
換句話說(shuō),這一步可能會(huì)生成 Bean 的代理對(duì)象,并將其作為“早期引用”暴露出去。
第三級(jí)緩存的存在意義就在于:當(dāng)出現(xiàn)循環(huán)依賴時(shí),如果另一個(gè) Bean 需要當(dāng)前 Bean 的引用,Spring 能通過(guò)第三級(jí)緩存中的 ObjectFactory 提前觸發(fā)代理邏輯,返回正確的引用(包括可能的代理對(duì)象),從而保證依賴注入和最終 Bean 一致性。
3.2 二級(jí)緩存能不能解決循環(huán)依賴問(wèn)題
事實(shí)上,從循環(huán)依賴本身的角度來(lái)看,二級(jí)緩存也完全可以解決問(wèn)題。
因?yàn)槿?jí)緩存的核心功能,是在 Bean 初始化之前允許返回一個(gè)“早期引用”。
如果我們直接在實(shí)例化之后、屬性注入之前,將早期引用放入二級(jí)緩存,同樣能夠?qū)崿F(xiàn)循環(huán)依賴的解環(huán)。
假設(shè)我們對(duì) Spring 的邏輯稍作改造,不使用三級(jí)緩存,而是直接在實(shí)例化后生成早期引用并放入二級(jí)緩存:
// AbstractAutowireCapableBeanFactory(偽代碼改造)
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addEarlySingletonObjects(beanName, getEarlyBeanReference(beanName, mbd, bean));
}
在這段偽代碼中,Spring 不再保存 ObjectFactory,而是直接調(diào)用
getEarlyBeanReference() 獲取早期引用(包括可能的 AOP 代理對(duì)象),
然后立即將其放入二級(jí)緩存 earlySingletonObjects 中,供其他 Bean 在依賴注入時(shí)使用。
從表面上看,這樣確實(shí)可以達(dá)到同樣的效果:
循環(huán)依賴照樣被解決;
AOP 代理也能提前生成;
性能上沒(méi)有任何差別(甚至更直接)。
3.3 三級(jí)緩存實(shí)際解決的問(wèn)題
Spring 通過(guò)三級(jí)緩存設(shè)計(jì)了一個(gè)延遲生成早期引用的機(jī)制:它既能解決循環(huán)依賴,又能在大多數(shù)情況下保持 Bean 生命周期和 AOP 代理邏輯的語(yǔ)義一致性。
具體來(lái)說(shuō):
在 正常創(chuàng)建流程 中,Bean 會(huì)嚴(yán)格遵循生命周期:實(shí)例化 → 屬性注入 → 初始化 → 后置處理器(生成 AOP 代理)。
三級(jí)緩存的作用是為 循環(huán)依賴 這種突發(fā)情況 提供一個(gè)彈性通道:當(dāng) Bean 之間存在循環(huán)依賴時(shí),Spring 可以通過(guò)三級(jí)緩存提前生成早期引用(可能是代理對(duì)象),從而打破循環(huán)依賴的僵局。
換句話說(shuō),三級(jí)緩存是一種 “在必要時(shí)允許突破生命周期規(guī)范的機(jī)制”,保證循環(huán)依賴能夠被安全解決,同時(shí)不會(huì)影響絕大多數(shù) Bean 的正常創(chuàng)建流程。
到此這篇關(guān)于對(duì)于SpringBoot的三層緩存的思考的文章就介紹到這了,更多相關(guān)SpringBoot三層緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 中 MyBatis 與 Spring 
這篇文章主要介紹了Spring Boot 中 MyBatis 與 Spring Data JPA 的優(yōu)缺點(diǎn)對(duì)比,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2025-05-05
Java RabbitMQ的工作隊(duì)列與消息應(yīng)答詳解
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
SpringBoot+Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)
這篇文章主要為大家詳細(xì)介紹了springBoot+Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
JAVA導(dǎo)出EXCEL表格的實(shí)例教學(xué)
在本文中我們給大家整理了關(guān)于JAVA導(dǎo)出EXCEL表格的實(shí)例教學(xué)以及相關(guān)知識(shí)點(diǎn),需要的朋友們學(xué)習(xí)下。2019-02-02
Springboot整合WebSocket實(shí)戰(zhàn)教程
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),這篇文章主要介紹了Springboot整合WebSocket實(shí)戰(zhàn)教程,需要的朋友可以參考下2023-05-05
解決Java?properties文件里面如何寫(xiě)"\"的問(wèn)題
由于properties使用“\”相當(dāng)于是java的轉(zhuǎn)義符,如果想要寫(xiě)出\的效果,只需修改相應(yīng)的寫(xiě)法即可,對(duì)java?properties文件里的"\"寫(xiě)法感興趣的朋友一起看看吧2022-04-04
圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的
這篇文章主要介紹了圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的,文中通過(guò)示例代碼和圖片介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Mybatis-Plus使用MetaObjectHandler實(shí)現(xiàn)自動(dòng)填充實(shí)體對(duì)象字段
在我們使用Mybatis-Plus時(shí),一些簡(jiǎn)單的CRUD,你會(huì)發(fā)現(xiàn)好多表,許多字段是重復(fù)的,如果我們每次更新或者新增,都要手動(dòng)賦值,那么會(huì)出現(xiàn)許多不必要的重復(fù)操作,所以本文介紹了Mybatis-Plus使用MetaObjectHandler實(shí)現(xiàn)自動(dòng)填充實(shí)體對(duì)象字段,需要的朋友可以參考下2024-11-11

