讀完這篇文章你將會收穫到
Spring
循環依賴可以分爲哪兩種Spring
如何解決setter
循環依賴Spring
爲何是三級緩存 , 二級不行 ?Spring
爲啥不能解決構造器循環依賴
概述
循環依賴就是循環引用,兩個或以上的 bean
相互持有對方。比如說 beanA
引用 beanB
, beanB
引用 beanC
, beanC
引用 beanA
, 它們之間的引用關係構成一個環。
Spring 如何解決循環依賴
Spring
中的循環依賴包括
- 構造器循環依賴
setter
循環依賴
構造器的依賴
Spring
對於構造器的依賴、無法解決。只會拋出 BeanCurrentlyInCreationException
異常。
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
setter 的循環依賴
不管是 autowireByName
還是 autowireByType
都是屬於這種。Spring
默認是能夠解決這種循環依賴的,主要是通過 Spring
容器提前暴露剛完成構造器注入但未完成其他步驟的 bean 來完成的。而且只能解決 singleton
類型的循環依賴、對於 prototype
類型的是不支持的,因爲 Spring
沒有緩存這種類型的 bean
Spring 是如何解決的
其實很簡單、在 Spring 獲取單例流程(一) 中我們曾提及過三級緩存
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// 這個bean 正處於 創建階段
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 併發控制
synchronized (this.singletonObjects) {
// 單例緩存是否存在
singletonObject = this.earlySingletonObjects.get(beanName);
// 是否運行獲取 bean factory 創建出的 bean
if (singletonObject == null && allowEarlyReference) {
// 獲取緩存中的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 將對象緩存到 earlySingletonObject中
this.earlySingletonObjects.put(beanName, singletonObject);
// 從工廠緩衝中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
Spring
解決 setter
循環依賴的關鍵點就是在這裏,主要是 singletonFactories
這個 Map
中
我們可以先梳理一下整體的流程
beanA --> beanB --> beanC -->beanA
以上面爲例子、我們先假設它們是構造器的循環依賴
Spring
初始化完成之後、接收到一個getBean
的調用請求、請求beanA
Spring
發現三級緩存中都沒有beanA
的存在、所以開始創建beanA
的流程- 將
beanA
放入到singletonsCurrentlyInCreation
集合中去、代表着beanA
正在創建中 - 兜兜轉轉,發現我要
new
一個beanA
的對象、我要先獲得一個beanB
的對象、好、我們就進行一個getBean(beanB)
Spring
發現三級緩存中都沒有beanB
的存在、所以開始創建beanB
的流程- 將
beanB
放入到singletonsCurrentlyInCreation
集合中去、代表着beanB
正在創建中 - 兜兜轉轉,發現我要
new
一個beanB
的對象、我要先獲得一個beanC
的對象、好、我們就進行一個getBean(beanC)
Spring
發現三級緩存中都沒有beanC
的存在、所以開始創建beanC
的流程- 將
beanC
放入到singletonsCurrentlyInCreation
集合中去、代表着beanC
正在創建中 - 兜兜轉轉,發現我要
new
一個beanC
的對象、我要先獲得一個beanA
的對象、好、我們就進行一個getBean(beanA)
Spring
發現三級緩存中都沒有beanA
的存在、所以開始創建beanA
的流程- 將
beanA
放入到singletonsCurrentlyInCreation
集合中去、但是在這個時候、插入到集合中失敗、直接拋出異常
而假如我們是一個 setter
的循環依賴
Spring
初始化完成之後、接收到一個getBean
的調用請求、請求beanA
- 先判斷三級緩存中有沒有
beanA
,如果沒有則往下進行 - 將
beanA
放入到singletonsCurrentlyInCreation
集合中去、代表着beanA
正在創建中 - 兜兜轉轉,終於創建了一個
beanA
, 但是這個時候的beanA
是一個不完整的狀態、因爲很多屬性沒有被賦值、比如說beanA
中的成員變量beanB
現在還是一個null
的狀態 - 然後判斷是否需要將當前創建的不完整的
beanA
加入到第三級緩存中,正常來說都是會被加入到第三級緩存中的 - 加入第三級緩存以後、進行一個屬性填充,這個時候發現需要填充一個
beanB
對象 - 然後如上面那樣、先看看三級緩存有沒有
beanB
,如果沒有則創建一個並不完整的beanB
、然後加入到第三級緩存中、然後發現需要填充一個beanC
的屬性 - 然後如上面那樣、先看看三級緩存有沒有
beanC
,如果沒有則創建一個並不完整的beanC
、然後加入到第三級緩存中、然後發現需要填充一個beanA
的屬性 - 這個時候,先看看三級緩存中有沒有
beanA
,發現在第三級緩衝中有不完整的beanA
、將其從第三級緩存中移除出來、放入到第二級緩存中,然後返回給beanC
用於填充屬性 - 然後
beanC
的 屬性填充完畢,則將其從singletonsCurrentlyInCreation
集合中移除掉,代表beanC
已經真正的創建好了 - 然後將
beanC
加入到第一級緩存中,並將其從第三級緩存中移除,並返回給beanB
,beanB
也如beanC
那樣處理 beanA
也如beanB
、beanC
那樣處理、加入到第一級緩存中、然後從第二級緩存中移除- 結束
其實上面的屁話又長又臭,但是流程還是非常簡單的
爲啥是三級緩存,二級不行嗎?
/**
* Cache of singleton objects: bean name to bean instance.
* 存放的是單例 bean、對應關係是 bean Name --> bean instance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* Cache of early singleton objects: bean name to bean instance.
* 存放的早期的 bean、對應的關係 也是 beanName --> bean instance
* 與 singletonObjects 區別在於 earlySingletonObjects 中存放的bean 不一定是完整的、
* bean 在創建過程中就加入到 earlySingletonObjects 中了、所以在bean創建過程中就可以通過getBean 方法獲取、
* 這個Map 也是解決循環依賴的關鍵所在
**/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* Cache of singleton factories: bean name to ObjectFactory.
* 存放的是 ObjectFactory 、可以理解爲創建單例bean的factory、對應關係是 bean name --> objectFactory
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
我們來看看從第三級緩存升級到第二級緩存究竟發生了什麼
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
// 默認實現
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
其實只要有二級緩存也是可以的,雖然可以達到解決 setter
循環依賴的問題、但是卻無法給用戶提供一個擴展接口(當存在循環依賴的)。
就好比說、上面的例子、在循環依賴的關係中,當 beanA
從第三級緩存升級到第二級緩存的時候,我們可以在其升級的時候去設置一些 beanA
的屬性或者做一些其他事情,我們只需要在 beanA 的類中實現 SmartInstantiationAwareBeanPostProcessor
接口即可
但是單純只有二級緩存的話,當我們創建好一個沒有完成初始化的 bean
的時候、要麼就直接調用 ObjectFactory
的 getObject
方法獲取經過回調的 bean
放入到第二級緩存(不管這個 bean
存不存在一個循環引用的關係鏈中),要麼就直接放剛剛創建好的沒有完成初始化的 bean
放入到第二級緩存。無論是哪種情況,都無法達到這樣一個需求:當存在循環依賴的時候,我們作爲用戶需要對其進行一些設置或者一些其他的操作
爲啥不能解決構造函數的循環依賴
如果按照解決 setter
循環依賴的流程、是否能夠解決?先將一個不完整的 bean
放入到第三級緩存中,然後提供出去給其他 bean
依賴。但是呢,問題是我無法創建出這麼一個不完整的 bean
在一個構造函數依賴的關係中,參數不全,再牛皮也不能把