Spring如何使用三級緩存解決循環(huán)依賴
前言
1. 什么是循環(huán)依賴
類A需要類B,我們就叫做類A依賴類B。簡單說就是依賴,或者和別的類相互依賴
1.1 互相依賴

1.2 遞歸依賴

2. Sping中循環(huán)依賴有什么問題?
在Spring中,循環(huán)依賴指的是兩個(gè)或多個(gè)Bean之間相互依賴形成的循環(huán)引用關(guān)系。具體來說,當(dāng)Bean A依賴于Bean B,而Bean B又依賴于Bean A時(shí),就形成了循環(huán)依賴。
只有單例的 Bean 才存在循環(huán)依賴的情況,原型(Prototype)情況下,Spring 會(huì)直接拋出異常。
循環(huán)依賴可能導(dǎo)致以下問題:
- 無法完成Bean的初始化:當(dāng)存在循環(huán)依賴時(shí),Spring容器無法確定先初始化哪個(gè)Bean,因?yàn)樗鼈兿嗷ヒ蕾嚕叶夹枰獙Ψ酵瓿沙跏蓟拍芾^續(xù)。這可能導(dǎo)致Bean的初始化過程無法完成,從而引發(fā)異常。
- 無限遞歸調(diào)用:當(dāng)存在循環(huán)依賴時(shí),Spring容器可能會(huì)陷入無限遞歸的調(diào)用中,導(dǎo)致系統(tǒng)堆棧溢出。這是因?yàn)槊看潍@取Bean時(shí),Spring容器需要檢測循環(huán)依賴并創(chuàng)建實(shí)例,但由于循環(huán)依賴的存在,無法正常創(chuàng)建實(shí)例,從而導(dǎo)致無限遞歸調(diào)用。

為了解決循環(huán)依賴問題,Spring使用了三級緩存和"提前暴露"的策略
3. 什么是三級緩存
對于創(chuàng)建單例Bean,Spring創(chuàng)建了三個(gè)容器來存儲(chǔ)不同時(shí)期的對象:
- ?級緩存 : Map<String,Object> singletonObjects,單例池,?于保存實(shí)例化、屬性賦值(注?)、初始化完成的 bean 實(shí)例
- ?級緩存 : Map<String,Object> earlySingletonObjects,早期曝光對象,?于保存實(shí)例化完成的 bean 實(shí)例
- 三級緩存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光對象??,?于保存 bean 創(chuàng)建??,以便于后?擴(kuò)展有機(jī)會(huì)創(chuàng)建代理對象

4. Spring 可以解決哪些情況的循環(huán)依賴?
Spring 不?持基于構(gòu)造器注?的循環(huán)依賴,假如 AB 循環(huán)依賴,其中一方使用構(gòu)造器注入,也是不支持的。

為什么呢?下面二級緩存會(huì)說明白。
二級緩存作用——普通循環(huán)依賴
只用一級緩存和二級緩存就能解決普通bean的循環(huán)依賴。
先回顧Bean對象創(chuàng)建的步驟:

二級緩存:又稱 半成品池 存放的是實(shí)例化,但未屬性賦值和初始化的Bean對象。一級緩存:又稱 單例池 存放的是完成屬性賦值和初始化的成品Bean,可以直接使用了。
那么二級緩存是如何解決普通Bean的循環(huán)依賴的?
實(shí)操環(huán)節(jié)
類A依賴類B,類B依賴類A。

1. 實(shí)例化類A對象
對象a被實(shí)例化出來,會(huì)被放到半成品池中,當(dāng)進(jìn)行下一步屬性賦值時(shí),發(fā)現(xiàn)依賴了類B,所以開始創(chuàng)建對象b。

如果對象b是在a的構(gòu)造函數(shù)中注入的,那就完了,a無法實(shí)例化,得先去實(shí)例化b,若是b也是構(gòu)造函數(shù)中注入的a,那就無解了。
public class A {
private B b;
@Inject
public A(B b) {
this.b = b;
}
}2. 實(shí)例化類B對象
對象b被實(shí)例化出來,也被放到半成品池中。下一步是屬性賦值,發(fā)現(xiàn)依賴了類A,會(huì)依次從?級到三級緩存查詢類A對象,最終會(huì)在半成品池中找到對象a,成功將它賦值到自己的屬性中。

3. B對象完成創(chuàng)建
對象b在經(jīng)過填充屬性、初始化后會(huì)從半成品池里挪到單例池中,可以直接使用了。

4.繼續(xù)創(chuàng)建A對象
這時(shí)候回過頭來繼續(xù)對象a的屬性注入,把對象b賦值給自己的屬性后再經(jīng)過初始化,對象a也從半成品池挪到單例池,對象a創(chuàng)建完成,對象b也跟著創(chuàng)建完成。

三級緩存作用——aop循環(huán)依賴
二級緩存仍然存在問題,它無法解決AOP代理問題。
1. AOP代理問題
AOP(面向切面)簡單的說,在不改源碼的情況下在原始方法前后加一些代碼。
它的底層是靠動(dòng)態(tài)代理實(shí)現(xiàn)的,即生成一個(gè)代理類,重新原始方法,真正使用的時(shí)候其實(shí)用的是代理類對象,而非原始類對象。
既然用的是代理類對象,單例池中應(yīng)該存放的就該是代理類對象。
二級緩存無法解決生成代理對象的問題,因?yàn)閯?chuàng)建對象的過程很復(fù)雜,每個(gè)代理類都需要一個(gè)工廠來專門生成代理類對象。

三級緩存又叫工廠池,就是用來存放生成代理類對象工廠的。

2. 何時(shí)生成代理對象
AOP是靠AOP處理器實(shí)現(xiàn)的,處理器有兩個(gè)生成代理對象的方式。
前置處理:在Bean對象初始化后后置處理:再Bean對象實(shí)例化前

Spring為了解決使用AOP的對象循環(huán)依賴的問題,使用了這兩種處理方式。
實(shí)操環(huán)節(jié)
類A依賴類B,類B依賴類A,類A使用了AOP。

1.實(shí)例化類A對象
首先把創(chuàng)建類A代理對象的工廠對象放到工廠池中。

類A實(shí)例化對象時(shí)發(fā)現(xiàn)依賴了類B,使用前置處理器生成A的代理對象,放在半成品池子中。

2. 實(shí)例化類B對象
對象b找到對象a,成功屬性賦值,再經(jīng)過初始化成功創(chuàng)建,挪到單例池中待用。
如果類B也使用了AOP,那么對象b在初始化后,會(huì)通過后置處理器生成動(dòng)態(tài)代理對象,放到單例池中。

3.繼續(xù)創(chuàng)建A對象
對象b創(chuàng)建完,接著回頭創(chuàng)建對象a,這個(gè)過程就很順利了。
當(dāng)對象a完成初始化以后,因?yàn)橐呀?jīng)是代理對象,就不會(huì)在走后置處理。
對象a、b都創(chuàng)建完,會(huì)清空在二級、三級池中的相關(guān)數(shù)據(jù),最終只在單例池中保留一份對象。

總結(jié)
盡管Spring提供了解決循環(huán)依賴的機(jī)制,但循環(huán)依賴本身是一個(gè)設(shè)計(jì)上的問題,可能導(dǎo)致代碼的可讀性和可維護(hù)性下降。因此,在編寫代碼時(shí),應(yīng)盡量避免出現(xiàn)循環(huán)依賴的情況。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java安全之Tomcat6 Filter內(nèi)存馬問題
這篇文章主要介紹了Java安全之Tomcat6 Filter內(nèi)存馬,通過本文探討下Tomcat6與Tomcat8之間的區(qū)別,主要看下tomcat6和tomcat8之間createFilterChain不相同的地方 看到ApplicationFilterFactory#createFilterChain,需要的朋友可以參考下2022-10-10
JAVA錯(cuò)誤:'無效目標(biāo)發(fā)行版?17'的解決方案
這篇文章主要給大家介紹了關(guān)于JAVA錯(cuò)誤:'無效目標(biāo)發(fā)行版?17'的解決方案,文中通過圖文介紹的非常詳細(xì),對大家學(xué)習(xí)或使用java具有一的的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-09-09
springboot微服務(wù)項(xiàng)目集成html頁面的實(shí)現(xiàn)
本文主要介紹了springboot微服務(wù)項(xiàng)目集成html頁面的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
Java實(shí)現(xiàn)字符串轉(zhuǎn)List的常見方式總結(jié)
在Java開發(fā)中,常需將字符串轉(zhuǎn)換為List類型,本文為大家介紹了幾種常見轉(zhuǎn)換方式,即Arrays.asList()方法,Stream API,Guava庫和Apache Commons Lang庫,感興趣的小伙伴可以了解下2026-03-03
Java BeanUtils.copyProperties的詳解
這篇文章主要介紹了Java BeanUtils.copyProperties的詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
mybatis mybatis-plus-generator+clickhouse自動(dòng)生成代碼案例詳解
這篇文章主要介紹了mybatis mybatis-plus-generator+clickhouse自動(dòng)生成代碼案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
詳解Java中格式化日期的DateFormat與SimpleDateFormat類
DateFormat其本身是一個(gè)抽象類,SimpleDateFormat 類是DateFormat類的子類,一般情況下來講DateFormat類很少會(huì)直接使用,而都使用SimpleDateFormat類完成,下面我們具體來看一下兩個(gè)類的用法:2016-05-05
idea中打開項(xiàng)目時(shí)import project和open區(qū)別詳解
本文主要介紹了idea中打開項(xiàng)目時(shí)import project和open區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
springboot項(xiàng)目中idea的pom.xml文件的引用標(biāo)簽全部爆紅問題解決
這篇文章主要介紹了springboot項(xiàng)目中idea的pom.xml文件的引用標(biāo)簽全部爆紅問題解決,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-12-12

