Spring容器三級(jí)緩存的使用及說明
Spring容器為了解決循環(huán)依賴問題,引入了三級(jí)緩存系統(tǒng)。這與Hibernate/MyBatis中的緩存概念不同,是Spring特有的設(shè)計(jì)。
1、緩存介紹

1.1、緩存分類
1.一級(jí)緩存(singletonObjects)
用途:
存放完全初始化好的單例 Bean,這些 Bean 已經(jīng)完成了所有的屬性注入和初始化操作,可以直接使用。
數(shù)據(jù)結(jié)構(gòu):
Map<String,Object>,鍵為 Bean 的名稱,值為對(duì)應(yīng)的 Bean 實(shí)例。
2.二級(jí)緩存(earlySingletonObjects)
用途:
存放早期曝光的 Bean 實(shí)例,這些 Bean 已經(jīng)被創(chuàng)建,但還沒有完成屬性注入和初始化操作。當(dāng)需要解決循環(huán)依賴時(shí),可以從這個(gè)緩存中獲取 Bean 的早期引用。
數(shù)據(jù)結(jié)構(gòu):
Map<String,Object>,鍵為 Bean 的名稱,值為對(duì)應(yīng)的 Bean 實(shí)例。
3.三級(jí)緩存(singletonFactories)
用途:
存放ObjectFactory對(duì)象,這些對(duì)象可以用來創(chuàng)建 Bean 的早期引用。也可以處理AOP代理等特殊情況
數(shù)據(jù)結(jié)構(gòu):
Map<String,ObjectFactory<?>>,鍵為 Bean 的名稱,值為對(duì)應(yīng)的ObjectFactory對(duì)象。
如下圖所示:

1.2、聯(lián)系
三級(jí)緩存是為了解決循環(huán)依賴問題而引入的,當(dāng)出現(xiàn)循環(huán)依賴時(shí),首先會(huì)從一級(jí)緩存中查找 Bean,如果找不到,會(huì)嘗試從二級(jí)緩存中查找,如果還是找不到,會(huì)從三級(jí)緩存中獲取ObjectFactory并創(chuàng)建 Bean 的早期引用,放入二級(jí)緩存中。

二級(jí)緩存中的 Bean 是從三級(jí)緩存中創(chuàng)建出來的早期引用,這些 Bean 還沒有完成屬性注入和初始化操作。
一級(jí)緩存中的 Bean 是最終可用的 Bean,這些 Bean 已經(jīng)完成了所有的屬性注入和初始化操作。

2、循環(huán)依賴
關(guān)于以下文章介紹的是Spring單例bean的循環(huán)依賴解決方案。
2.1、循環(huán)依賴場(chǎng)景
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}2.2、解決流程圖示
有對(duì)象A和對(duì)象B,分別相互依賴。
如下圖所示:

1、A對(duì)象緩存查詢

依次查詢一級(jí)、二級(jí)、三級(jí)查詢,由于開始,三種緩存里面分別沒有A對(duì)象的緩存。
2、A對(duì)象創(chuàng)建對(duì)象
通過反射,將a對(duì)象放到三級(jí)緩存里面。
3、A對(duì)象屬性填充
在填充屬性的時(shí)候,會(huì)發(fā)現(xiàn)A對(duì)象需要依賴B對(duì)象,因此重復(fù)剛才A對(duì)象的操作步驟。
如下圖所示:

1、B對(duì)象緩存查詢
先從緩存查詢B對(duì)象的三級(jí)緩存,由于首次查詢,b對(duì)象的一級(jí)、二級(jí)、三級(jí)緩存均為空。
2、B對(duì)象創(chuàng)建對(duì)象
然后,創(chuàng)建B對(duì)象,將b對(duì)象的引用放到三級(jí)緩存里,此時(shí)三級(jí)緩存里面同時(shí)存放了A、B對(duì)象的引用。

3、B對(duì)象屬性填充
在進(jìn)行B對(duì)象填充屬性的時(shí)候,發(fā)現(xiàn)依賴于A。
B依賴A的緩存查詢
然后重復(fù)執(zhí)行緩存查詢的操作,此時(shí)由于前面A、B三級(jí)緩存分別在創(chuàng)建對(duì)象的時(shí)候,都放在了三級(jí)里面。
B依賴A的二級(jí)緩存
因此通過將三級(jí)緩存,放入二級(jí)緩存里,同時(shí)刪除三級(jí)的a對(duì)象。

4、B對(duì)象初始化
然后在進(jìn)行b對(duì)象的初始化,此時(shí)@postConstruct就在這里執(zhí)行,完成B對(duì)象的初始化。

5、B對(duì)象緩存轉(zhuǎn)移
如下圖所示:

將b對(duì)象的三級(jí)緩存、二級(jí)緩存移除掉,同時(shí)寫入一級(jí)緩存里面。


4、A對(duì)象初始化
5、A對(duì)象緩存轉(zhuǎn)移
刪除A對(duì)象的三級(jí)緩存、二級(jí)緩存、同時(shí)寫入到1級(jí)緩存。

總結(jié):A、B循環(huán)依賴的流程圖如下所示:

2.3、代碼示例
下面是一個(gè)簡單的 Java 代碼示例,模擬 Spring 容器的三級(jí)緩存機(jī)制來解決循環(huán)依賴問題:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
// 模擬 Bean 定義
class BeanDefinition {
private String beanName;
private Class<?> beanClass;
public BeanDefinition(String beanName, Class<?> beanClass) {
this.beanName = beanName;
this.beanClass = beanClass;
}
public String getBeanName() {
return beanName;
}
public Class<?> getBeanClass() {
return beanClass;
}
}模擬Spring容器
// 模擬 Spring 容器
class BeanFactory {
// 一級(jí)緩存
private final Map<String, Object> singletonObjects = new HashMap<>();
// 二級(jí)緩存
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三級(jí)緩存
private final Map<String, Supplier<Object>> singletonFactories = new HashMap<>();
// Bean 定義集合
private final Map<String, BeanDefinition> beanDefinitions = new HashMap<>();
// 注冊(cè) Bean 定義
public void registerBeanDefinition(BeanDefinition beanDefinition) {
beanDefinitions.put(beanDefinition.getBeanName(), beanDefinition);
}
// 獲取 Bean
public Object getBean(String beanName) {
// 先從一級(jí)緩存中查找
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 再從二級(jí)緩存中查找
singletonObject = earlySingletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 從三級(jí)緩存中查找
Supplier<Object> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
// 創(chuàng)建早期引用
singletonObject = singletonFactory.get();
// 將早期引用放入二級(jí)緩存
earlySingletonObjects.put(beanName, singletonObject);
// 從三級(jí)緩存中移除
singletonFactories.remove(beanName);
return singletonObject;
}
// 創(chuàng)建 Bean
BeanDefinition beanDefinition = beanDefinitions.get(beanName);
if (beanDefinition != null) {
try {
// 創(chuàng)建 Bean 實(shí)例
Object bean = beanDefinition.getBeanClass().newInstance();
// 將 Bean 工廠放入三級(jí)緩存
singletonFactories.put(beanName, () -> bean);
// 模擬屬性注入,可能會(huì)出現(xiàn)循環(huán)依賴
// 這里簡單處理,不進(jìn)行實(shí)際的屬性注入
// ...
// 屬性注入完成后,將 Bean 放入一級(jí)緩存
singletonObjects.put(beanName, bean);
// 從二級(jí)緩存中移除
earlySingletonObjects.remove(beanName);
return bean;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
}測(cè)試類:
// 測(cè)試類
public class Main {
public static void main(String[] args) {
BeanFactory beanFactory = new BeanFactory();
// 注冊(cè) Bean 定義
beanFactory.registerBeanDefinition(new BeanDefinition("beanA", BeanA.class));
beanFactory.registerBeanDefinition(new BeanDefinition("beanB", BeanB.class));
// 獲取 Bean
Object beanA = beanFactory.getBean("beanA");
Object beanB = beanFactory.getBean("beanB");
System.out.println("BeanA: " + beanA);
System.out.println("BeanB: " + beanB);
}
}BeanA類:
// 示例 Bean A
class BeanA {
private BeanB beanB;
public BeanA() {
}
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}BeanB類:
// 示例 Bean B
class BeanB {
private BeanA beanA;
public BeanB() {
}
public BeanA getBeanA() {
return beanA;
}
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}2.4、代碼解釋
- BeanDefinition類用于存儲(chǔ) Bean 的定義信息,包括 Bean 的名稱和類。
- BeanFactory類模擬了 Spring 容器的功能,包含了三級(jí)緩存和 Bean 定義集合。
- getBean方法用于獲取 Bean 實(shí)例,首先從一級(jí)緩存中查找,如果找不到,再從二級(jí)緩存中查找,如果還是找不到,從三級(jí)緩存中獲取ObjectFactory并創(chuàng)建 Bean 的早期引用,放入二級(jí)緩存中。
- Main類用于測(cè)試BeanFactory的功能,注冊(cè) Bean 定義并獲取 Bean 實(shí)例。
流程:
+---------------------+
| 一級(jí)緩存 (singletonObjects) |
| 存放完全初始化的 Bean |
+---------------------+
^
|
|
+---------------------+
| 二級(jí)緩存 (earlySingletonObjects) |
| 存放早期曝光的 Bean |
+---------------------+
^
|
|
+---------------------+
| 三級(jí)緩存 (singletonFactories) |
| 存放 ObjectFactory 對(duì)象 |
+---------------------+這個(gè)圖展示了 Spring 容器的三級(jí)緩存結(jié)構(gòu),
1. 一級(jí)緩存位于最上層,存放完全初始化的 Bean;
2.二級(jí)緩存位于中間,存放早期曝光的 Bean;
3.三級(jí)緩存位于最下層,存放ObjectFactory對(duì)象。
4.當(dāng)需要解決循環(huán)依賴時(shí),會(huì)從三級(jí)緩存中獲取ObjectFactory并創(chuàng)建 Bean 的早期引用,放入二級(jí)緩存中,最終將完全初始化的 Bean 放入一級(jí)緩存中。
3、三級(jí)緩存
1、原因
為什么要使用三級(jí)緩存,二級(jí)不可以嗎?
盡管二級(jí)緩存能解決部分循環(huán)依賴問題,但 Spring 引入三級(jí)緩存主要是為了支持 AOP(面向切面編程)。
若 Bean 需要 AOP 代理(如事務(wù)管理),代理對(duì)象需要在依賴注入時(shí)動(dòng)態(tài)生成。三級(jí)緩存中的ObjectFactory可以延遲生成代理對(duì)象,確保依賴注入時(shí)使用代理后的實(shí)例。
具體原因如下:
1、支持 AOP 代理:
在 Spring 中,當(dāng)一個(gè) Bean 需要進(jìn)行 AOP 代理時(shí),代理對(duì)象和原始 Bean 對(duì)象可能是不同的。
如果只使用二級(jí)緩存,在早期曝光時(shí)放入的是原始 Bean 實(shí)例,那么在后續(xù)的屬性注入過程中,其他 Bean 引用的就是原始 Bean 而非代理對(duì)象,這會(huì)導(dǎo)致 AOP 失效。
而三級(jí)緩存(singletonFactories)存放的是ObjectFactory,可以在需要時(shí)通過ObjectFactory的getObject方法來創(chuàng)建代理對(duì)象,保證在出現(xiàn)循環(huán)依賴時(shí),其他 Bean 引用的是正確的代理對(duì)象。
2、延遲創(chuàng)建代理對(duì)象:
使用三級(jí)緩存可以實(shí)現(xiàn)延遲創(chuàng)建代理對(duì)象。只有在真正出現(xiàn)循環(huán)依賴且需要獲取早期引用時(shí),才會(huì)調(diào)用ObjectFactory的getObject方法來創(chuàng)建代理對(duì)象,避免了不必要的代理對(duì)象創(chuàng)建,提高了性能。
綜上所述,雖然二級(jí)緩存能解決部分循環(huán)依賴問題,但為了支持 AOP 代理和延遲創(chuàng)建代理對(duì)象,Spring 引入了三級(jí)緩存機(jī)制。
4、使用范圍
Spring只能解決單例Bean通過Setter/字段注入的循環(huán)依賴。
1.構(gòu)造器注入的循環(huán)依賴
@Component
public class A {
private B b;
public A(B b) { this.b = b; } // 構(gòu)造器注入
}
@Component
public class B {
private A a;
public B(A a) { this.a = a; } // 構(gòu)造器注入
}原因:構(gòu)造器注入需要先完成Bean的實(shí)例化,無法提前暴露半成品。
2.多例Bean(@Scope("prototype"))
Spring不緩存多例Bean,因此無法解決循環(huán)依賴。
5、建議
- 盡量避免循環(huán)依賴:代碼結(jié)構(gòu)不合理時(shí)容易引發(fā)循環(huán)依賴,建議通過重構(gòu)解決。
- 優(yōu)先使用Setter/字段注入:構(gòu)造器注入雖然安全,但無法解決循環(huán)依賴。
- 利用@Lazy延遲加載:對(duì)某個(gè)Bean添加
@Lazy,讓Spring延遲注入,打破循環(huán)。
@Component
public class A {
@Lazy // 延遲注入B
@Autowired
private B b;
}6、擴(kuò)展
1、多個(gè)AOP的順序怎么定
通過**@Order注解來設(shè)置增強(qiáng)類優(yōu)先級(jí):這個(gè)值越小優(yōu)先級(jí)越高**!
@Order(3)
public class UserProxy {}
@Order(1)
public class PersonProxy {}
2、如何讓兩個(gè)Bean按順序加載
- 1、使用 @DependsOn
@Component
@DependsOn({"beanB", "beanC"}) // 確保beanB和beanC先加載
public class BeanA {
// ...
}
@Component
public class BeanB {
// ...
}
@Component
public class BeanC {
// ...
}- 2、實(shí)現(xiàn)PriorityOrdered或Ordered接口
對(duì)于實(shí)現(xiàn)了特定接口的Bean,可以控制它們的初始化順序:
@Component
public class BeanOne implements PriorityOrdered {
@Override
public int getOrder() {
return 1; // 數(shù)字越小優(yōu)先級(jí)越高
}
}
@Component
public class BeanTwo implements Ordered {
@Override
public int getOrder() {
return 2;
}
}- 3、使用@Order注解
適用于某些特定場(chǎng)景,如攔截器、AOP切面等的順序控制
@Component
@Order(1)
public class FirstBean {
// ...
}
@Component
@Order(2)
public class SecondBean {
// ...
}- 4、讓后加載的類依賴先加載的類
@Component
public class A {
@Autowire
private B b;
}
總結(jié)
Spring通過三級(jí)緩存+提前暴露半成品對(duì)象解決循環(huán)依賴問題,核心目的是處理AOP代理對(duì)象的唯一性。雖然理論上兩級(jí)緩存可以解決部分場(chǎng)景,但三級(jí)緩存是Spring設(shè)計(jì)上的必要選擇。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring的連接數(shù)據(jù)庫以及JDBC模板(實(shí)例講解)
下面小編就為大家?guī)硪黄猄pring的連接數(shù)據(jù)庫以及JDBC模板(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10
Spring?Boot?Admin?監(jiān)控指標(biāo)接入Grafana可視化的實(shí)例詳解
Spring Boot Admin2 自帶有部分監(jiān)控圖表,如圖,有線程、內(nèi)存Heap和內(nèi)存Non Heap,這篇文章主要介紹了Spring?Boot?Admin?監(jiān)控指標(biāo)接入Grafana可視化,需要的朋友可以參考下2022-11-11
sqlserver和java將resultSet中的記錄轉(zhuǎn)換為學(xué)生對(duì)象
這篇文章主要介紹了如何利用sqlserver和java將resultSet中的記錄轉(zhuǎn)換為學(xué)生對(duì)象,附有超詳細(xì)的代碼,需要的朋友可以參考一下,希望對(duì)你有所幫助2021-12-12
Spring?Boot配置內(nèi)容加密實(shí)現(xiàn)敏感信息保護(hù)
之前我們講過的配置相關(guān)知識(shí)都是Spring?Boot原生就提供的,而今天我們將介紹的功能并非Spring?Boot原生就支持,但卻非常有用:配置內(nèi)容的加密2021-11-11
IntelliJ IDEA同步代碼時(shí)版本沖突而產(chǎn)生出的incoming partial文件問題的解決辦法
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA同步代碼時(shí)版本沖突而產(chǎn)生出的incoming partial文件問題的解決辦法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10
詳解spring-boot集成elasticsearch及其簡單應(yīng)用
本篇文章主要介紹了詳解spring-boot集成elasticsearch及其簡單應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
使用IDEA向Gitee提交SpringBoot項(xiàng)目進(jìn)行遠(yuǎn)程管理
本文主要介紹了使用IDEA向Gitee提交SpringBoot項(xiàng)目進(jìn)行遠(yuǎn)程管理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01

