文章目錄
- 前言
- 一、什麼是循環依賴
-
- 1. 循環依賴有幾種情況
- 二、循環依賴的解決
-
- 1.getSingleton(beanName)
- 2 為什麼不能解決構造函數注入方式的循環依賴
- 2.疑問
前言
上篇我們講了bean的初始化流程,要搞懂循環依賴,必須要先清除流程,不清楚的請移步上一篇文章。
這篇我們就來講講Spring是如何解決循環依賴的
一、什麼是循環依賴
循環依賴,其實就是循環引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。如下圖所示:
循環依賴,其實就是一個死循環的過程,在初始化 A 的時候發現引用了 B,這時就會去初始化 B,然後又發現 B 引用 C,跑去初始化 C,初始化 C 的時候發現引用了 A,則又會去初始化 A,依次循環永不退出,除非有終結條件。
1. 循環依賴有幾種情況
有兩種
- 基于構造函數的注入方式的循環依賴
- 基于屬性注入的循環依賴
Spring隻能解決屬性注入的循環依賴,構造函數注入方式的循環依賴是沒法解決的,原因等我們分析完循環依賴後再來個總結.
其實倒不是說不能使用構造函數注入,而且Spring從4.3開始推薦使用構造函數注入的方式,舉例具體來說
A 依賴 B, B又依賴A
下面這幾種情況
- A中采用構造函數注入依賴B, B中采用構造函數依賴A, 無法解決
- A中采用構造函數注入依賴B, B中采用屬性注入A, 可以解決
- A中采用屬性注入依賴B, B中采用構造函數依賴A 無法解決
- A中采用屬性注入依賴B. B中采用屬性注入A 可以解決
細心的異能已經發現了,隻要B中采用構造函數注入A,就無法解決,A采用什麼方式則沒什麼影響
下面我們以A,B 都采用屬性注入的方式且無AOP代理的情況下來看Spring是如何解決循環依賴的,這也是最常見的方式
二、循環依賴的解決
1.getSingleton(beanName)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
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;
}
話不多說,直接上圖,看不清的點選放大來看
但是這隻是最簡單的循環依賴的一種情況, 有AOP的會更加複雜,以後有機會再來更新
2 為什麼不能解決構造函數注入方式的循環依賴
如果熟悉了循環依賴的解決流程,那麼這個問題自然也很好回答, 因為如果采用構造函數注入的方式,那麼會去看構造函數的參數是否初始化,如果沒有會初始化這個bean,如此一來,便陷入了死循環,沒法提前曝光半初始化的bean了,
說的簡單點就是,指定了構造函數就不會走預設的無參構造,就無法走下面的提前曝光,加入三級緩存,也就無法解決循環依賴了
2.疑問
這個疑問我在上篇文章中提過,就是這段代碼
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
這裡也與循環依賴有關
//到這裡又會走getSingleton(beanName, false),是不是很熟悉,沒錯就是我們進doGetBean時一開始會走的從緩存中取bean的方法
//不過之前傳的是true,這裡傳的是false
//先提前說明下這個方法的流程,舉例說明
//對于循環依賴: A依賴B, B依賴A,且屬性注入(構造函數注入無法解決嘛)
//初始化A, 走到填充屬性時,會去初始化B, 由于B中又依賴的A, 又去getBean(A), 此時就會走 getSingleton(beanName, true)
//而此時容器中一,二級緩存中沒有A執行個體的,隻有三級緩存中有,就會把三級緩存中那個半初始化的beanA取出(注意:此時會把A放在的二級緩存中,從三級緩存中移除)
//然後B的屬性填充完畢, 然後走初始化完成, 當B走到下面這段代碼時, 即走到getSingleton(beanName, false)時, 一二級緩存均為空,
//但是傳參是false,是以Object earlySingletonReference 為null,那麼傳回的就是初始化完成的exposedObject
//即exposedObject = initializeBean(beanName, exposedObject, mbd);
// 最後後會把初始化好的B放入一級緩存中
//然後繼續走之前的A屬性填充(留意一下,A填充好了),初始化,但是當A走到這段代碼時,因為之前往二級緩存中放了A,
//是以此時取二級緩存不為空,會傳回緩存中的這個引用,而且如果這個引用和增強後的bean是一個對象執行個體,那麼會傳回這個引用
//此時有人可能會有疑問: 緩存中的這個bean不是半初始化的麼,怎麼傳回使用了呢?
//這是因為,二級緩存中存放的是A的引用,在之前這個引用指向的對象執行個體被填充了啊,這就是之前讓你留意一下的原因
//不過讀到這裡我有個疑問: 為什麼Spring要再去緩存中取一下這個bean呢?我直接使用初始化傳回的那個bean嘛,就是exposedObject,
//為什麼要現在這樣設計呢?還查下緩存,然後再比較bean有沒有變,變了傳回exposedObject, 沒變傳回緩存中的bean
//我看有的部落格說的是此處代碼的作用是: 循環依賴檢查,判斷是否需要抛出異常
//存疑?有知道的小夥伴可以留言告訴我,我感覺這裡是和AOP代理有關