希望之光永遠向著目標清晰的人敞開。
1. 迴圈依賴概述
迴圈依賴通俗講就是迴圈引用,指兩個或兩個以上物件的bean相互引用對方,A依賴於B,B依賴於A,最終形成一個閉環。
Spring迴圈依賴的場景有兩種:
- 構造器的迴圈依賴
- field 屬性的迴圈依賴
對於構造器的迴圈依賴,Spring 是無法解決,只能丟擲 BeanCurrentlyInCreationException 異常;對於field 屬性的迴圈依賴,Spring 只解決 scope 為 singleton 的迴圈依賴,對於scope 為 prototype 的 bean Spring 無法解決,直接丟擲 BeanCurrentlyInCreationException 異常。下面重點分析屬性依賴的情況。
2. 迴圈依賴執行流程
以上流程圖針對如下程式碼進行演示:
@Component
public class CircularRefA {
public CircularRefA() {
System.out.println("============CircularRefA()===========");
}
//這裡會觸發CircularRefB型別的getBean操作
@Autowired
private CircularRefB circularRefB;
}
@Component
public class CircularRefB {
public CircularRefB() {
System.out.println("============CircularRefB()===========");
}
//又會觸發A的getBean操作
@Autowired
private CircularRefA circularRefA;
}
step1
: A類例項化執行,第一次三個快取中都沒有,會走doGetBean
一路走到createBeanInstance
,完成無參建構函式例項化,並在addSingletonFactory
中設定三級快取;
step2
:A類populateBean
進行依賴注入,隨後觸發了B類屬性的getBean
操作;
step3
: B類與A類類似,第一次三個快取中也都沒有,無參建構函式例項化後,設定三級快取,將自己加入三級快取;
step4
:B類populateBean
進行依賴注入,這裡觸發了A類屬性的getBean
操作;
step5
: A類之前正在建立,此時已經是第二次進入,由於一級二級快取中都沒有,會從三級快取中獲取,並且允許 bean提前暴露則從三級快取中拿到物件工廠,從工廠中拿到物件成功後,升級到二級快取,並刪除三級快取;若以後有別的類引用的話就從二級快取中進行取;
step6
:B類拿到了A的提前暴露例項注入到A類屬性中了,此時完成B類的例項化;
step7
:A類之前依賴B類,B的例項化完成,進而促使A的例項化也完成,並且此時A的類B屬性已經有值,A類繼續走後續的afterSingletonCreateion與addSingleton方法,刪除正在建立快取中的例項,並將例項從二級快取移入以及快取,同時刪除二三級快取;
以上是A類例項化的全過程,下面會針對原始碼逐一分析。
3. 原始碼分析
首先建立A的例項,需要從A的getBean
方法開始,到doGetBean
,第一次優先從快取中取,進入getSingleton
方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級快取singletonObjects中取
Object singletonObject = this.singletonObjects.get(beanName);
// 一級快取為空,且單例物件正在建立
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從二級快取earlySingletonObjects中取
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果二級快取也為空,且允許bean提前暴露
if (singletonObject == null && allowEarlyReference) {
// 從三級快取singletonFactories中取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果三級快取不為空
if (singletonFactory != null) {
// 呼叫getEarlyBeanReference方法提前暴露bean
singletonObject = singletonFactory.getObject();
// 提前暴露的bean放入二級快取,
this.earlySingletonObjects.put(beanName, singletonObject);
// 將提前暴露的bean從三級快取中刪除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
這個方法主要是從三個快取中獲取,分別是:singletonObjects
、earlySingletonObjects
、singletonFactories
,三者定義如下:
/** 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 HashMap<>(16);
意義如下:
- singletonObjects:單例物件的cache
- singletonFactories : 單例物件工廠的cache
- earlySingletonObjects :提前暴露的單例物件的Cache
解決迴圈依賴的關鍵是三個快取,其中一級快取singletonObjects
存放完全例項化的物件,物件以及其依賴的屬性都有值;二級快取earlySingletonObjects
存放半例項化的物件,相當於在記憶體開闢了空間,已完成建立,但是還未進行屬性賦值,可以提前暴露使用;三級快取singletonFactories
為物件工廠,用來建立提前暴露的bean並放入二級快取中。
首次初始化A時,三個快取中都沒有物件,會進入如下getSingleton(String beanName, ObjectFactory<?> singletonFactory)
方法,該方法第二個引數是個函式式介面,當內部呼叫getObject
方法時,會呼叫createBean
方法:
// 建立bean例項
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
......
先進入getSingleton
方法:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// bean單例建立前
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 呼叫createBean方法建立bean
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 建立完成後要從正在例項化的bean集合singletonsCurrentlyInCreation中刪除該bean
afterSingletonCreation(beanName);
}
if (newSingleton) {
// bean加入快取
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
該方法完成流程圖中的四個步驟:
step1
:bean單例建立前,將beanName放入singletonsCurrentlyInCreation
快取;
step2
: singletonFactory.getObject()
呼叫外面函式式介面中的createBean
方法建立bean;
step3
:afterSingletonCreation
方法將beanName從singletonsCurrentlyInCreation快取刪除,表示已建立完;
step4
: 例項化後加入一級快取,二三級快取刪除。
以上1、3、4涉及程式碼如下:
protected void beforeSingletonCreation(String beanName) {
// 將正在建立的bean放入快取singletonsCurrentlyInCreation
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
protected void afterSingletonCreation(String beanName) {
// 正在建立中的快取容器singletonsCurrentlyInCreation清除剛剛建立的bean
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 一級快取存放bean
this.singletonObjects.put(beanName, singletonObject);
// 三級快取移除bean
this.singletonFactories.remove(beanName);
// 二級快取移除bean
this.earlySingletonObjects.remove(beanName);
//
this.registeredSingletons.add(beanName);
}
}
其中step2
流程較長,當A第一次例項化時,走到建立無參建構函式例項化createBeanInstance
,隨後會走addSingletonFactory
方法:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
從這段程式碼我們可以看出singletonFactories
這個三級快取才是解決迴圈依賴的關鍵,該程式碼在createBeanInstance
方法之後,也就是說這個bean其實已經被建立出來了,但是它還不是很完美(沒有進行屬性填充和初始化),但是對於其他依賴它的物件而言已經足夠了(可以根據物件引用定位到堆中物件),能夠被認出來了,所以Spring在這個時候選擇將該物件提前曝光出來讓大家認識認識。當三級快取有值後,後面如果再次用到該bean的時候,會從三級快取中,並通過提前暴露,升級到二級快取中,到這裡我們發現三級快取singletonFactories
和二級快取earlySingletonObjects
中的值都有出處了,那一級快取在哪裡設定的呢?就是在A建立完,並把A依賴的屬性B也建立完後,B有依賴於A,再次進入A後,A直接從二級快取中獲取,從而促使B物件建立完,隨即A也就建立完成,A完成createBean後走上面的step4
中的addSingletion
方法,完成一級快取的設定。
4.總結
Spring在建立bean的時候並不是等它完全完成,而是在建立過程中將建立中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 快取中),這樣一旦下一個 bean 建立的時候需要依賴 bean ,則直接使用 ObjectFactory 的 getObject() 獲取了,故在快取中使用三級快取獲取到例項,並將例項升級到二級快取,供後續例項如需二次使用時,可直接從二級快取中取,待例項完全建立後,升級到一級快取,並清理二級三級快取,總而言之提前暴露三級快取,以及一二三級快取的綜合使用是解決迴圈依賴的關鍵,各級快取各司其職,又能夠相互呼應,spring的設計實在精妙,給我們自己設計專案提供了一種優秀的思考方式。