天天看點

源碼解析Spring循環依賴一、什麼是循環依賴二、Spring如何檢測循環依賴三、Spring能處理哪些循環依賴的情況

源碼解析Spring循環依賴

  • 一、什麼是循環依賴
  • 二、Spring如何檢測循環依賴
  • 三、Spring能處理哪些循環依賴的情況
    • 4.3 Spring的三級緩存
      • 4.3.1 三級緩存的使用過程
      • 4.3.2 三級緩存使用過程中重要的三個方法
      • 4.3.3 為什麼一定要三個層級的緩存

聲明:下文出現的Spring源碼都基于Spring 5.3.7版本

一、什麼是循環依賴

所謂循環依賴是指兩個或兩個以上的bean互相引用對方,最終形成了一個依賴的閉環。

說到循環依賴好像就會有一種先入為主的印象,總感覺是自己在代碼實作上出現了問題,其實循環依賴是與實際業務相關的,某些業務在實作的時候可能就是需要bean A依賴bean B,同時bean B又依賴bean A,這是正常的情況,開發者在程式設計的時候可以盡量避免循環依賴,但如果遇到某個業務實在無法避免也沒關系,Spring本身可以自動解決一部分循環依賴的情況,隻要正确的使用Spring,即使有循環依賴也完全可以正常運作,關鍵在于我們需要弄清楚Spring中有哪些循環依賴的情況,以及Spring無法解決哪些情況。

二、Spring如何檢測循環依賴

Spring檢測循環依賴的方式也比較簡單,在建立bean的時候可以給一個标簽,表明目前bean正在建立,然後遞歸的建立依賴的bean,遞歸建立的過程中如果發現bean已經處于建立中的狀态,就說明存在循環依賴了,存在循環依賴并不會直接報錯,隻有Spring無法自動解決的時候才會報錯。

三、Spring能處理哪些循環依賴的情況

先說結論:如果存在一個依賴閉環,我們稱這個閉環中最先被Spring建立的Bean為閉環中的第一個Bean(Spring按照字典序建立Bean),如果這個Bean是構造器注入,其它Bean不管是構造器注入還是Set方法注入,spring都無法自動解決,并且會在啟動的時候報錯提醒。簡單來說就是隻要存在依賴閉環,并且閉環中第一個bean是構造器注入,其它bean不管是構造器注入還是Set方法注入,這些情況Spring都無法自動解決,其餘情況Spring都能自動解決。

在分析原因之前先來說說Spring處理循環依賴的主要理論依據,我們知道Spring Bean說到底也是java對象,bean之間的依賴關系說到底也就是對象之間的引用關系,比如Bean A依賴Bean B,實際上就是A對象中的一個屬性保持了對B對象的引用,從這個角度來說Spring建立bean并處理依賴關系的過程可以簡單的認為就是執行個體化一個java對象并填充java對象屬性的過程。那java對象的屬性可以在什麼時候填充呢?最為常見的有兩種,第一種是使用有參構造器,在執行個體化的時候就填充上;第二種是先通過無參構造器把對象執行個體化出來,然後通過屬性的set方法填充,在Spring中這兩種方式分别對應構造器注入和Set方法注入。Set方法注入相比構造器注入的不同就在于使用Set方法進行注入的時候bean已經執行個體化出來了,隻是屬性暫時還沒填充完整,這種bean被稱為提前暴露的bean。bean之間建立依賴關系隻要有一個對象的引用就可以了,至于這個對象的屬性是否已經填充完整并不關心,這些屬性可以延後填充, 是以提前暴露出來的bean雖然不完整但已經可以注入到其它bean了。

這就是Spring自動解決循環依賴的理論依據:當出現循環依賴的時候可以先把依賴閉環中第一個bean執行個體化并提前暴露出來,這樣閉環上依賴這個bean的bean就可以建立完成,這個bean建立出來之後,依賴這個bean的下一個bean也就可以建立出來,進而整個遞歸能夠順利進行下去,當跳出最後一層遞歸之後第一個bean依賴的bean也已經建立出來了,隻要将這個bean再注入到第一個bean中整個閉環上所有bean就都建立完成了。文字描述比較抽象,舉個例子,假如A依賴B,B依賴C,C又依賴A,先把A執行個體化并暴露出來,這個樣A的引用已經有了并且能被其它bean發現。由于A依賴B,是以接下來嘗試去建立B,發現B又依賴C,是以嘗試去建立C,發現C又依賴A,這個時候發現A的引用已經有了,是以直接注入到C,C能順利建立,C建立完成之後B也就能順利建立,B建立完之後,将B注入A,A也就建立完成了,整個依賴閉環上的Bean都建立成功了。關鍵就在于依賴閉環的第一個Bean能不能提前暴露出來,為什麼一定是第一個呢?因為最後一個bean總會依賴第一個Bean,如果第一個bean不能提前暴露出來,最後一個bean就無法建立完成,進而導緻整個遞歸過程失敗。哪種情況第一個bean無法提前暴露出來呢?沒錯,就是第一個bean使用構造器注入的時候。

4.3 Spring的三級緩存

我們稱Spring為容器,容器從字面意義上就是存放東西的器皿,那Spring作為容器存放了什麼呢?沒錯,就是Bean,最終所有的單例bean都會存放到Spring的單例池中。但是在Spring啟動的時候單例bean并不是一開始就存放到了單例池的,而是使用了三級緩存,建立的過程中會按照需要放入到不同的緩存,但最終都會移入到單例池中,單例池實際上就是三級緩存中的第一級緩存,看DefaultSingletonBeanRegistry類的源碼:

// DefaultSingletonBeanRegistry類源碼

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
           

所謂三級緩存就是三個Map,Spring官方并沒有直接指明層級,不過根據代碼,singletonObjects總是最先被查詢,我們姑且稱為一級緩存,以此類推,earlySingletonObjects為二級緩存,singletonFactories為三級緩存。它們各自的含義如下:

  • singletonObjects:singletonObjects即一級緩存,也是最終存放所有單例bean的單例池,是以這個緩存中存放的是完整的單例bean,什麼是完整的單例bean呢?就是經曆了bean生命周期完整的建立過程,能夠傳遞使用的bean。所有建立完成的單例bean最終都會放入到這個緩存中,而二級緩存earlySingletonObjects和三級緩存singletonFactories都隻是臨時的緩存,容器建立bean的過程中會用到,但是最終這兩個緩存中的bean都會移入一級緩存也就是單例池中,是以最終這兩個緩存都是空的。
  • earlySingletonObjects:earlySingletonObjects即二級緩存,這個緩存用來存放執行個體化完成但是還沒将屬性裝配完成的單例bean,也就是前面提到的那些提前暴露的bean,隻有出現循環依賴的時候才需要把bean提前暴露出來,是以也隻有出現循環依賴的時候才會用到二級緩存。
  • singletonFactories:singletonFactories即三級緩存,這個緩存如果翻譯成中文是"bean工廠緩存",聽名字很容易與BeanFactory混淆,BeanFactory是Spring容器的頂層接口,BeanFactory就是Spring容器,但是這裡的**"bean工廠緩存"與容器BeanFactory沒有任何關系**,三級緩存這個Map的Value是ObjectFactory接口類型,ObjectFactory中隻有getObject()一個接口方法,與Spring容器的頂層接口BeanFactory是兩個不同的類,是以沒有任何關系。我們從三級緩存中取出緩存的ObjectFactory之後,需要調用getObject()方法來擷取bean對象,是以這個ObjectFactory相當于一個臨時的容器,這個容器中隻裝了一個bean,是以三級緩存實際存的是目前bean的一個臨時容器,如果要從三級緩存中取出bean,就要先取出這個臨時容器,然後從臨時容器中把bean取出來,這和前兩級緩存是不太一樣的,一、二級緩存的Map的value直接存的就是bean對象,取出來就是bean對象,後面會講解Spring為什麼要這麼設計。三級緩存singletonFactories也是一個臨時的緩存,任何一個單例bean執行個體化完成之後都會先包裝為一個ObjectFactory對象,然後放入到這個緩存。

4.3.1 三級緩存的使用過程

在沒有AOP的情況下我們來看三級緩存的實際使用過程,這裡描述四種情況下緩存的使用涵蓋了所有的場景:第一種情況是Bean A和Bean B沒有依賴關系;第二種情況是Bean A依賴Bean B,Bean B不依賴Bean A;第三種情況Bean A不依賴Bean B,Bean B依賴Bean A;第四種情況為Bean A和Bean B互相依賴,也就是循環依賴。

在往下閱讀之前先聲明一下,Spring建立一個bean可以簡化的看成兩個步驟,第一步是執行個體化bean;第二步是填充屬性,也就是處理依賴關系,本文中将第二步填充屬性稱之為"裝配bean",是以後文出現"裝配bean"的描述,含義就是處理bean的依賴關系。同時當讀者在下文中看到"建立bean"字樣的時候,要了解這個"建立"實際是包含了兩個步驟的,兩步都完成才算建立完成。

在下文中還會出現"移入"的字樣,比如"把bean從三級緩存中移入到一級緩存"這樣的描述,這裡的"移入"指的是DefaultSingletonBeanRegistry類中addSingleton方法的邏輯:

// DefaultSingletonBeanRegistry類中的addSingleton方法
protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}
           

這段代碼實際上是把bean放入到一級緩存,同時從二級緩存和三級緩存中移除,整體來看相當于将bean從二級緩存或三級緩存移入到一級緩存,是以使用"移入"這樣一個詞來描述這個過程。

聲明結束之後來看四種情況下三級緩存的具體使用情況:

  • a、Bean A和Bean B沒有依賴關系

    Spring按照字典序建立bean,是以Spring先建立bean A。建立的第一步先執行個體化bean A,執行個體化完成之後把bean A包裝為ObjectFactory對象放入到三級緩存singletonFactories中,然後進行bean A的裝配,裝配過程發現bean A沒有依賴,裝配結束;然後把bean A從三級緩存singletonFactories直接移入到一級緩存,也就是單例池中, bean A建立完成。然後開始建立bean B,bean B是同樣的過程,是以不再贅述。

    這個過程中緩存使用是: 三級緩存singletonFactories —> 一級緩存singletonObjects

  • b、Bean A依賴Bean B,Bean B不依賴Bean A

    Spring同樣首先建立Bean A,第一步先執行個體化Bean A,執行個體化完成之後把bean A包裝為ObjectFactory對象放入到三級緩存singletonFactories中,然後進行bean A的裝配,發現bean A依賴bean B,Spring會去容器中查找bean B,也就是從這三個緩存中查找,會發現三個緩存中都沒有bean B,Spring就知道bean B還沒建立,是以先去建立bean B,同樣先執行個體化bean B,執行個體化完成之後把bean B包裝為ObjectFactory對象放入到三級緩存singletonFactories中,然後進行bean B的裝配,發現bean B沒有依賴,bean B裝配結束,把bean B從三級緩存直接移入到一級緩存,bean B建立完成;回過頭來繼續裝配bean A,在bean A中注入bean B,完成之後bean A裝配結束;然後把bean A從三級緩存移入到一級緩存,bean A建立結束。

    這個過程中緩存使用是:三級緩存singletonFactories —> 一級緩存singletonObjects

  • c、Bean A不依賴Bean B,Bean B依賴Bean A

    Spring首先建立Bean A,第一步先執行個體化Bean A,執行個體化完成之後把bean A包裝為ObjectFactory對象放入到三級緩存singletonFactories中;然後進行bean A的裝配,發現bean A沒有依賴,裝配結束,再把bean A從三級緩存直接移入到一級緩存,bean A建立結束。然後建立bean B,同樣先執行個體化bean B,執行個體化結束之後将bean B包裝為ObjectFactory對象放入到三級緩存singletonFactories中;然後進行bean B的裝配,發現bean B依賴bean A,spring就去容器中查找bean A,并在一級緩存中找到,将bean A注入到bean B中,裝配結束;然後将bean B從三級緩存移入到一級緩存,bean B建立結束。

    這個過程中緩存使用是:三級緩存singletonFactories —> 一級緩存singletonObjects

  • d、Bean A和Bean B互相依賴

    Spring首先建立bean A,第一步先執行個體化bean A,執行個體化完成之後把bean A包裝為ObjectFactory對象放入到三級緩存singletonFactories中;然後進行bean A的裝配,發現bean A依賴bean B,spring會去容器中查找bean B,發現bean B還沒有建立,是以先去建立bean B,同樣先執行個體化bean B,執行個體化結束之後将bean B包裝為ObjectFactory對象放入到三級緩存中;然後進行bean B的裝配,發現bean B依賴bean A,spring會去容器中查找bean A,也就是從三個緩存中查找,會發現bean A在第三級緩存中,第三級緩存存放的是ObjectFactory對象,取出ObjectFactory對象之後調用getObject方法擷取到bean A,拿到bean A之後會放入到二級緩存earlySingletonObjects中,順便在三級緩存中把ObjectFactory對象移除,這個過程相當于将bean A從第三級緩存移入到了第二級緩存,盡管bean A還沒裝配結束,但總之是擷取到了bean A,将擷取到的bean A注入到bean B,bean B裝配結束;裝配結束之後将bean B從三級緩存移入到一級緩存,bean B建立完成;然後回過頭來繼續裝配bean A,将bean B注入bean A,bean A裝配結束;将bean A從二級緩存移入到一級緩存,bean A建立結束。

    這個過程中bean A的緩存使用:三級緩存singletonFactories —> 二級緩存earlySingletonObjects —> 一級緩存singletonObjects

    bean B的緩存使用:三級緩存singletonFactories —> 一級緩存singletonObjects

以上過程都是通過調試代碼得出的,我們知道Spring容器的啟動是從refresh()方法開始的,在refresh()方法中會調用finishBeanFactoryInitialization()方法,該方法會建立出所有非懶加載的bean,将這個方法作為入口進行調試,我制作了一張長圖來展示完整的調用過程,如果感興趣可以用同樣的方式進行調試,由于圖實在太長,是以請自行點開檢視:spring建立bean流程

4.3.2 三級緩存使用過程中重要的三個方法

整個調用過程還是很複雜的,但是其中最重要的就隻有三個方法,這三個方法中主要步驟我都進行了注釋,由于三個方法都比較長,是以後面在貼源碼的時候省略掉了中間那些對了解無關緊要的代碼,比如異常處理等,如果想看完整的代碼,建議直接看源碼。一定要跟着源碼調試一遍,調試是學習源碼最好的方法。

第一個重要的方法是DefaultSingletonBeanRegistry類的getSingleton方法,這個方法是建立bean的入口,這個方法最終會調用到AbstractAutowireCapableBeanFactory類的doCreateBean方法來真正建立和裝配bean,也就是第二個重要的方法。注意DefaultSingletonBeanRegistry類還有一個重載的getSingleton方法,這是第三個重要的方法(後面有展示),這兩個方法的參數不同,功能也不一樣,後一個getSingleton方法用來從緩存中查找bean。

/**
* DefaultSingletonBeanRegistry類的getSingleton方法,這個方法是建立bean的入口,後面還有一個getSingleton方法,兩個方法的參數不同,功能也不一樣,後一個getSingleton方法用來從緩存中查找bean
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null) {
	    // 1、此處getObject方法最終會調用到AbstractAutowireCapableBeanFactory類的doCreateBean方法,也就是真正去建立一個bean的地方,也就是第二個重要的方法
		singletonObject = singletonFactory.getObject();
		newSingleton = true;
		
		if (newSingleton) {
		    // 2、當bean建立完成之後将bean從三級緩存或二級緩存移入到一級緩存,整個建立過程就結束了
			addSingleton(beanName, singletonObject);
		}
	}
	return singletonObject;
}
           

第二個重要的方法是AbstractAutowireCapableBeanFactory類的doCreateBean方法,這個方法用來真正執行個體化bean、處理依賴關系、增強bean的功能。處理依賴關系的時候會去查找它依賴的bean,是以最終又會調用到DefaultSingletonBeanRegistry類中用于查找bean的getSingleton方法,也就是第三個重要的方法。

/**
* AbstractAutowireCapableBeanFactory類的doCreateBean方法,這個方法用來真正執行個體化和裝配bean
*/
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	BeanWrapper instanceWrapper = null;
	...
	if (instanceWrapper == null) {
	    // 1、執行個體化一個bean
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	Object bean = instanceWrapper.getWrappedInstance();
    ...
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
	    // 2、将執行個體化的bean包裝為ObjectFactory對象,放入到三級緩存singletonFactories中
	    // 注意第二個參數是一個lambda表達式,實際上是ObjectFactory函數式接口,這個接口隻有getObject()一個方法,用來傳回bean,如果沒有AOP,傳回的就是上面執行個體化的bean對象,但如果有AOP,傳回的将是代理對象
		// 由于是一個lambda表達式,是以getEarlyBeanReference()方法并不會立刻執行
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	Object exposedObject = bean;
	
	// 3、populateBean方法用來裝配bean,也就是依賴注入,當發現依賴關系之後會去容器中查找依賴的bean,查找的過程最終會調用到DefaultSingletonBeanRegistry類中重載的的getSingleton方法,也就是第三個重要的方法
	populateBean(beanName, mbd, instanceWrapper);
	// 4、bean功能的增強(aware接口、bean後置處理器、初始化方法),AOP的代理對象在這裡生成
	exposedObject = initializeBean(beanName, exposedObject, mbd);
	
	if (earlySingletonExposure) {
	    // 從緩存中擷取對象,注意第二參數是false,意味着隻會從一級緩存和二級緩存擷取,由于此時目前bean還沒有建立完成,是以一級緩存必然沒有目前bean,那麼這次調用其實含義是從二級緩存中擷取bean
	    // 是以如果此時bean還在三級緩存中,這裡傳回值earlySingletonReference将是null
	    // 但如果傳回值不為null,說明bean從三級緩存移到了二級緩存,必然是循環依賴的場景,如果bean有AOP,這裡earlySingletonReference将是代理後的bean;如果沒有AOP,earlySingletonReference依然是原本的bean
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
			...
		}
	}
	... // 省略了部分代碼
	return exposedObject;
}
           

第三個重要的方法是DefaultSingletonBeanRegistry類中重載的getSingleton方法,這個方法用來從緩存中查找bean。

/**
* DefaultSingletonBeanRegistry類中重載的getSingleton方法,這個方法用來從緩存中查找bean
*/
// 注意第二個參數,如果第二個參數為false,意味着隻會從前兩級緩存中查找,不會去第三級緩存查找
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從第一級緩存中查找
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
	    // 從第二級緩存中查找
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				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();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}
           

4.3.3 為什麼一定要三個層級的緩存

從上面使用三級緩存的過程中可以看到如果沒有AOP,二級緩存earlySingletonObjects隻有在有循環依賴的情況下才會使用,但是不知道大家有沒有一個疑問,為什麼一定要有這個二級緩存earlySingletonObjects呢?比如A依賴B,B依賴A的循環依賴場景,假如A和B都是set方法注入并且Spring隻有一級緩存和三級緩存,Spring先執行個體化A,A執行個體化結束之後包裝為BeanFactory對象放入三級緩存,然後裝配A的時候發現A依賴B,就去嘗試建立B,先執行個體化B,B執行個體化結束之後包裝為BeanFactory對象放入到三級緩存中,然後裝配B,發現B依賴A,A已經在三級緩存中了,直接将A從三級緩存中擷取到完成B的裝配,B建立完成,将B從三級緩存移入到一級緩存中;然後回過頭來繼續裝配A,從一級緩存中擷取到B完成A的裝配,A建立完成,将A從三級緩存移入到一級緩存,A和B都建立完成了,這樣一分析是不是發現似乎隻有一級緩存和三級緩存也可以解決循環依賴的問題?

确實,如果僅僅隻是為了處理循環依賴的情況,兩個層級的緩存已經完全足夠,根本不需要三個層級的緩存,使用三個層級的緩存也不是為了提升效率之類想當然的理由。

其實前面的描述中已經提示了答案,我在前面描述的時候都加了一個前提條件:“如果沒有AOP”,Spring一定要使用三級緩存的目的其實是為了處理有AOP情況下的循環依賴問題。注意,如果僅僅隻有AOP而沒有循環依賴,也不會用到二級緩存,一定是有循環依賴才需要用到二級緩存。

我們知道AOP的底層原理是動态代理,通過代理來對功能進行增強,是以如果有AOP最終使用的必然是代理對象,比如我們對A進行了AOP,那麼最終注入到B的必然是A的代理對象。那麼A的代理對象是什麼時候生成的呢?如果沒有循環依賴,AOP的代理對象是在Bean後置處理器的後方法中生成的,Spring執行Bean後置處理的後方法是在initializeBean方法中,而initializeBean方法是在populateBean方法的後面,populateBean方法用來處理依賴關系,是以沒有循環依賴的情況下Spring是先進行依賴注入,然後再生成代理對象。

if (instanceWrapper == null) {
    // 1、執行個體化一個bean
	instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // 2、将執行個體化的bean放入到三級緩存singletonFactories中,注意并不是直接把bean放入一個容器,第二個參數是一個lambda表達式,getEarlyBeanReference()方法并不會立刻執行
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
    // 3、populateBean方法用來裝配bean,也就是依賴注入
	populateBean(beanName, mbd, instanceWrapper);
	// 4、bean功能的增強(aware接口、bean後置處理器的前後方法、初始化方法)
	exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
	...
}
           

但假如有循環依賴的情況,比如A和B循環依賴,且A有AOP,A先執行個體化出來,執行到注釋3的位置,發現A依賴B,就去嘗試遞歸建立B,B先執行個體化,執行個體化之後執行到注釋3的位置,發現B依賴A,這個時候B需要的是A的代理對象,但是A的代理對象正常情況下要在在注釋4的位置才生成,A還沒執行到注釋4的位置呢,那怎麼辦呢,隻能是B在注釋3的位置也就是處理依賴關系的時候就提前把A的代理對象給生成出來,要怎麼提前生成出來呢?B是從三級緩存中擷取A,還記得第三級緩存是什麼嗎?對,是ObjectFactory接口,是以B從三級緩存中擷取A是先擷取到ObjectFactory對象,然後調用ObjectFactory對象的getObject方法來擷取A,getObject方法的具體實作是getEarlyBeanReference()方法:

// 将執行個體化的bean包裝為ObjectFactory對象放入三級緩存,這裡是一個lambda表達式,表達式的内容就是getObject方法的具體實作
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
           

我們來看getEarlyBeanReference()方法的邏輯:

/**
* AbstractAutowireCapableBeanFactory中的getEarlyBeanReference方法
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	// Spring使用AnnotationAwareAspectJAutoProxyCreator這個Bean後置處理來處理AOP,AnnotationAwareAspectJAutoProxyCreator實作了SmartInstantiationAwareBeanPostProcessor接口,是以這裡if條件就是判斷目前bean是否需要進行AOP處理
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
		    // 最終會調用到AbstractAutoProxyCreator類中的getEarlyBeanReference方法,生成代理對象
			exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

/**
* AbstractAutoProxyCreator類中的getEarlyBeanReference方法
*/
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	// 注意earlyProxyReferences存放的是原來的bean,而不是代理過後的bean
	this.earlyProxyReferences.put(cacheKey, bean);
	// 生成代理對象
	return wrapIfNecessary(bean, beanName, cacheKey);
}

           

可以看到在getEarlyBeanReference()方法中判斷如果目前bean有AOP,那麼就會生成代理對象,否則就直接傳回原對象。回到之前的場景,B從三級緩存中先擷取到ObjectFactory對象,然後調用ObjectFactory對象的getObject()方法,由于A有AOP,是以getObject()方法傳回的是代理對象,正常執行下去b會用代理對象完成依賴注入,然後B的建立過程完成,跳出遞歸後會繼續執行A的流程,将B注入A,完成依賴關系的裝配,然後執行到注釋4的位置,也就是initializeBean()方法,這個方法正常情況下會在bean後置處理器的後方法中生成代理對象,但是代理對象已經提前生成過了,如果再生成一個代理對象就不對了,是以這裡不會再生成代理對象,而是傳回原對象,看源碼:

/**
* AbstractAutoProxyCreator類中的bean後置處理器中的後方法
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		// 有AOP的情況下會調用getEarlyBeanReference方法,在getEarlyBeanReference方法中會将原本的bean放入到earlyProxyReferences中,是以remove出來的bean和參數中的bean是同一個,條件不成立,是以不會再生成一個新的代理對象
		// 如果沒有aop,就不會調用getEarlyBeanReference方法,earlyProxyReferences是空的,remove出來是null,條件成立,就會生成一個代理對象
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}
           

也就是說調用initializeBean()方法傳回的仍然是原bean,但是最終需要的又是代理過的對象,那怎麼擷取代理過的對象呢?還得從緩存中擷取,此時一級緩存中還沒有,三級緩存中存儲的仍然是原本的bean,如果總共隻有兩級緩存,是沒辦法擷取到代理後的對象的,是以就需要一個另外的緩存來存儲代理過後的對象,也就是二級緩存。實際上B從三級緩存中擷取ObjectFactory對象,然後調用getObject方法擷取到A的代理對象之後,就将代理對象放入到了二級緩存,是以之後隻需要從二級緩存中來擷取到代理後的bean就可以了:

// 4、如果有AOP,initializeBean不會再生成代理對象,是以exposedObject仍然是原本的bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
if (earlySingletonExposure) {
    // 從緩存中擷取bean,注意這裡第二個參數是false,意味着隻會從一級和二級緩存中擷取bean,前面處理依賴關系的時候已經将代理對象放入到了二級緩存,是以這裡實際上就是從二級緩存中把代理過後的對象給取出來
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		if (exposedObject == bean) {
		    // 将代理過後的對象賦給exposedObject
			exposedObject = earlySingletonReference;
		}
		...
	}
}
...
return exposedObject;
           

最後将這個傳回exposedObject對象放入到一級緩存(單例池)中,bean A就建立結束了。

總結一下,在有循環依賴并且第一個bean有AOP的情況下,需要将第一個bean提前暴露出來,并且提前暴露出來的還要是代理過後的bean,這個暴露出來的bean必須有一個地方存儲,由于第三級緩存中存放的是原始的bean,而且bean沒有裝配完成的情況下也不能放入到一級緩存中,是以必須要要有二級緩存來存放提前暴露出來的代理過後的bean,這才是必須使用三個緩存的原因。

是以Spring使用三級緩存不是為了什麼提升效率之類想當然的理由,而是有明确目的的。但其實看了源碼就知道完成同樣的功能用兩級緩存也不是絕對不可以,不過那就是另一套實作方式了,任何時候實作相同的功能都可以有不同的方式,沒有什麼是絕對的,最終經受考驗的是哪種方式效率更高、更容易讓人了解。