希望之光永遠向着目標清晰的人敞開。
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的設計實在精妙,給我們自己設計項目提供了一種優秀的思考方式。