Spring 循環(huán)依賴:三級(jí)緩存的獨(dú)特優(yōu)勢(shì)與二級(jí)緩存的局限
前言
在 Java 開發(fā)領(lǐng)域,Spring 框架以其強(qiáng)大的功能和高度的靈活性被廣泛應(yīng)用。其中,循環(huán)依賴問題是 Spring 在 Bean 管理過程中必須妥善處理的關(guān)鍵挑戰(zhàn)之一。Spring 采用了獨(dú)特的三級(jí)緩存機(jī)制來有效應(yīng)對(duì)這一難題。
什么是Spring的循環(huán)依賴
圖片
循環(huán)依賴,簡(jiǎn)單來說,就是兩個(gè)或多個(gè) Bean 之間相互持有對(duì)方的引用,形成一個(gè)閉環(huán)。例如,Bean A 依賴于 Bean B,而 Bean B 又依賴于 Bean A,這就構(gòu)成了一個(gè)典型的循環(huán)依賴場(chǎng)景。在 Spring 容器初始化這些 Bean 時(shí),如果不能妥善處理循環(huán)依賴,將會(huì)導(dǎo)致程序陷入無限循環(huán),無法完成 Bean 的創(chuàng)建與初始化,進(jìn)而使應(yīng)用程序無法正常啟動(dòng)。
注意:
Spring解決循環(huán)依賴是有一定限制的:首先就是要求互相依賴的
Bean必須要是單例的Bean。另外就是依賴注入的方式不能都是構(gòu)造函數(shù)注入的方式。
為什么只支持單例
Spring循環(huán)依賴的解決方案主要是通過對(duì)象的提前暴露來實(shí)現(xiàn)的。當(dāng)一個(gè)對(duì)象在創(chuàng)建過程中需要引用到另一個(gè)正在創(chuàng)建的對(duì)象時(shí),Spring會(huì)先提前暴露一個(gè)尚未完全初始化的對(duì)象實(shí)例,以解決循環(huán)依賴的問題。這個(gè)尚未完全初始化的對(duì)象實(shí)例就是半成品對(duì)象。
在 Spring 容器中,單例對(duì)象的創(chuàng)建和初始化只會(huì)發(fā)生一次,并且在容器啟動(dòng)時(shí)就完成了。這意味著,在容器運(yùn)行期間,單例對(duì)象的依賴關(guān)系不會(huì)發(fā)生變化。因此,可以通過提前暴露半成品對(duì)象的方式來解決循環(huán)依賴的問題。
相比之下,原型對(duì)象的創(chuàng)建和初始化可以發(fā)生多次,并且可能在容器運(yùn)行期間動(dòng)態(tài)地發(fā)生變化。因此,對(duì)于原型對(duì)象,提前暴露半成品對(duì)象并不能解決循環(huán)依賴的問題,因?yàn)樵诤罄m(xù)的創(chuàng)建過程中,可能會(huì)涉及到不同的原型對(duì)象實(shí)例,無法像單例對(duì)象那樣緩存并復(fù)用半成品對(duì)象。
因此,Spring只支持通過單例對(duì)象的提前暴露來解決循環(huán)依賴問題。
為什么不支持構(gòu)造函數(shù)注入
Spring無法解決構(gòu)造函數(shù)的循環(huán)依賴,是因?yàn)樵趯?duì)象實(shí)例化過程中,構(gòu)造函數(shù)是最先被調(diào)用的,而此時(shí)對(duì)象還未完成實(shí)例化,無法注入一個(gè)尚未完全創(chuàng)建的對(duì)象,因此Spring容器無法在構(gòu)造函數(shù)注入中實(shí)現(xiàn)循環(huán)依賴的解決。
什么是Spring的三級(jí)緩存
在Spring的BeanFactory體系中,BeanFactory是Spring IoC容器的基礎(chǔ)接口,其DefaultSingletonBeanRegistry類實(shí)現(xiàn)了BeanFactory接口,并且維護(hù)了三級(jí)緩存:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
  //一級(jí)緩存,保存完成的Bean對(duì)象
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  //三級(jí)緩存,保存單例Bean的創(chuàng)建工廠
  private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  //二級(jí)緩存,存儲(chǔ)"半成品"的Bean對(duì)象
  private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
}singletonObjects是一級(jí)緩存,存儲(chǔ)的是完整創(chuàng)建好的單例bean對(duì)象。在創(chuàng)建一個(gè)單例bean時(shí),會(huì)先從singletonObjects中嘗試獲取該bean的實(shí)例,如果能夠獲取到,則直接返回該實(shí)例,否則繼續(xù)創(chuàng)建該bean。earlySingletonObjects是二級(jí)緩存,存儲(chǔ)的是尚未完全創(chuàng)建好的單例bean對(duì)象。在創(chuàng)建單例bean時(shí),如果發(fā)現(xiàn)該bean存在循環(huán)依賴,則會(huì)先創(chuàng)建該bean的半成品對(duì)象,并將半成品對(duì)象存儲(chǔ)到earlySingletonObjects中。當(dāng)循環(huán)依賴的bean創(chuàng)建完成后,Spring會(huì)將完整的bean實(shí)例對(duì)象存儲(chǔ)到singletonObjects中,并將earlySingletonObjects中存儲(chǔ)的代理對(duì)象替換為完整的bean實(shí)例對(duì)象。這樣可以保證單例bean的創(chuàng)建過程不會(huì)出現(xiàn)循環(huán)依賴問題。singletonFactories是三級(jí)緩存,存儲(chǔ)的是單例bean的創(chuàng)建工廠。當(dāng)一個(gè)單例bean被創(chuàng)建時(shí),Spring會(huì)先將該bean的創(chuàng)建工廠存儲(chǔ)到singletonFactories中,然后再執(zhí)行創(chuàng)建工廠的getObject()方法,生成該bean的實(shí)例對(duì)象。在該bean被其他bean引用時(shí),Spring會(huì)從singletonFactories中獲取該bean的創(chuàng)建工廠,并將這個(gè)早期引用放入二級(jí)緩存earlySingletonObjects中,同時(shí)從三級(jí)緩存中移除BeanA的工廠對(duì)象。
以下是DefaultSingletonBeanRegistry#getSingleton方法,代碼中,包括一級(jí)緩存、二級(jí)緩存、三級(jí)緩存的處理邏輯,該方法是獲取bean的單例實(shí)例對(duì)象的核心方法:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 首先從一級(jí)緩存中獲取bean實(shí)例對(duì)象,如果已經(jīng)存在,則直接返回
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 如果一級(jí)緩存中不存在bean實(shí)例對(duì)象,而且當(dāng)前bean正在創(chuàng)建中,則從二級(jí)緩存中獲取bean實(shí)例對(duì)象
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 如果二級(jí)緩存中也不存在bean實(shí)例對(duì)象,并且允許提前引用,則需要在鎖定一級(jí)緩存之前,
            // 先鎖定二級(jí)緩存,然后再進(jìn)行一系列處理
            synchronized (this.singletonObjects) {
                // 進(jìn)行一系列安全檢查后,再次從一級(jí)緩存和二級(jí)緩存中獲取bean實(shí)例對(duì)象
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 如果二級(jí)緩存中也不存在bean實(shí)例對(duì)象,則從三級(jí)緩存中獲取bean的ObjectFactory,并創(chuàng)建bean實(shí)例對(duì)象
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            // 將創(chuàng)建好的bean實(shí)例對(duì)象存儲(chǔ)到二級(jí)緩存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 從三級(jí)緩存中移除bean的ObjectFactory
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}二級(jí)緩存
圖片
其實(shí),使用二級(jí)緩存也能解決循環(huán)依賴的問題,一級(jí)緩存用于存儲(chǔ)已經(jīng)完全初始化好的 Bean 實(shí)例,這些 Bean 可以直接被應(yīng)用程序使用。二級(jí)緩存則存儲(chǔ)那些已經(jīng)實(shí)例化,但尚未完成屬性注入和其他初始化操作的 Bean,即 半成品Bean。
當(dāng) Spring 容器創(chuàng)建一個(gè) Bean 時(shí),首先會(huì)嘗試從一級(jí)緩存中獲取該 Bean。如果一級(jí)緩存中不存在,再去二級(jí)緩存中查找。若在二級(jí)緩存中找到,則返回這個(gè) 半成品Bean,等待后續(xù)完成剩余的初始化步驟。
但是如果完全依靠二級(jí)緩存解決循環(huán)依賴,意味著當(dāng)我們依賴了一個(gè)代理類的時(shí)候,就需要在Bean實(shí)例化之后完成AOP代理。而在Spring的設(shè)計(jì)中,為了解耦Bean的初始化和代理,是通過AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來在Bean生命周期的最后一步來完成AOP代理的。
因?yàn)樵?/span>Spring的初始化過程中,他是不知道哪些Bean可能有循環(huán)依賴的,那么,這時(shí)候Spring面臨兩個(gè)選擇:
- 不管有沒有循環(huán)依賴,都提前把代理對(duì)象創(chuàng)建出來,并將代理對(duì)象緩存起來,出現(xiàn)循環(huán)依賴時(shí),其他對(duì)象直接就可以取到代理對(duì)象并注入。
 - 不提前創(chuàng)建代理對(duì)象,在出現(xiàn)循環(huán)依賴時(shí),再生成代理對(duì)象。這樣在沒有循環(huán)依賴的情況下,
Bean就可以按著Spring設(shè)計(jì)原則的步驟來創(chuàng)建。 
二級(jí)緩存看上去比較簡(jiǎn)單,但是他也意味著Spring需要在所有的bean的創(chuàng)建過程中就要先生成代理對(duì)象再初始化,那么這就和Spring的aop的設(shè)計(jì)原則是相悖的。
AOP代理問題:當(dāng)Bean涉及AOP代理時(shí),二級(jí)緩存的局限性就凸顯出來了。在Spring中,AOP通過代理機(jī)制為Bean添加額外的功能,如事務(wù)管理、日志記錄等。在循環(huán)依賴場(chǎng)景下,如果使用二級(jí)緩存,可能會(huì)出現(xiàn)獲取到的Bean不是代理對(duì)象的情況。因?yàn)槎?jí)緩存中的Bean在被放入時(shí),可能還未經(jīng)過AOP代理處理。當(dāng)其他Bean依賴這個(gè)未代理的Bean時(shí),后續(xù)在使用該Bean的代理功能時(shí)就會(huì)出現(xiàn)問題,導(dǎo)致AOP功能無法正常發(fā)揮。- 破壞設(shè)計(jì)原則:
Spring的設(shè)計(jì)理念強(qiáng)調(diào)將Bean的創(chuàng)建和初始化過程進(jìn)行解耦,以提高代碼的可維護(hù)性和擴(kuò)展性。二級(jí)緩存機(jī)制在某些情況下可能會(huì)破壞這一設(shè)計(jì)原則。由于需要在Bean未完全初始化時(shí)就將其放入二級(jí)緩存,可能會(huì)導(dǎo)致在后續(xù)的初始化過程中,需要對(duì)這些 半成品Bean進(jìn)行額外的特殊處理,增加了代碼的復(fù)雜性和耦合度,不符合Spring設(shè)計(jì)的初衷。 

而Spring為了不破壞AOP的代理設(shè)計(jì)原則,則引入第三級(jí)緩存,在三級(jí)緩存中保存對(duì)象工廠,因?yàn)橥ㄟ^對(duì)象工廠我們可以在想要?jiǎng)?chuàng)建對(duì)象的時(shí)候直接獲取對(duì)象。有了它,在后續(xù)發(fā)生循環(huán)依賴時(shí),如果依賴的Bean被AOP代理,那么通過這個(gè)工廠獲取到的就是代理后的對(duì)象,如果沒有被AOP代理,那么這個(gè)工廠獲取到的就是實(shí)例化的真實(shí)對(duì)象。
總結(jié)
Spring 選擇使用三級(jí)緩存而不是二級(jí)緩存來解決循環(huán)依賴問題,是基于對(duì) AOP 代理支持和遵循自身設(shè)計(jì)原則的綜合考量。三級(jí)緩存機(jī)制通過巧妙地分離 Bean 的實(shí)例化、初始化以及代理對(duì)象的創(chuàng)建過程,在復(fù)雜的循環(huán)依賴場(chǎng)景下,能夠確保 Bean 的正確創(chuàng)建和初始化,同時(shí)保證 AOP 功能的正常運(yùn)行。















 
 
 










 
 
 
 