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

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

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

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

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

1、A對象緩存查詢

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

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

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

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

5、B對象緩存轉移
如下圖所示:

將b對象的三級緩存、二級緩存移除掉,同時寫入一級緩存里面。


4、A對象初始化
5、A對象緩存轉移
刪除A對象的三級緩存、二級緩存、同時寫入到1級緩存。

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

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

