什麼是循環依賴?
當多個Bean互相依賴時則構成了循環依賴,例如A,B兩個Bean。其中A中存在屬性B,B中存在屬性A,當Spring在執行個體化A時發現A中存在屬性B,就去執行個體化B,執行個體化B時又發現存在屬性A,一直在循環注入依賴,導緻循環依賴問題出現。
Spring全家桶學習筆記+大廠面試真題共享!
Spring是怎麼解決循環依賴的?
Spring中會通過各種Bean中間狀态來達到Bean還未執行個體化完成時提前将Bean提前注入到依賴Bean的屬性中,假設說Bean有三種狀态分别是青年态(一級緩存)、胚胎态(二級緩存)、小蝌蚪态(三級緩存)其中青年态代表Bean已經執行個體化完成,可以直接使用了,胚胎态代表Bean已經存在了但是還在建立中,還未建立完畢,小蝌蚪态代表還未開始建立,但是随時可以進行建立,三個狀态就類似于三個等級,可以逐漸提升從小蝌蚪狀态提升到胚胎狀态然後再提升到青年态,然後Spring開始建立Bena時會提前将Bean存放到小蝌蚪态的緩存集合中,當發現存在循環依賴時會使用存在于小蝌蚪狀态緩存集合中的Bean,提前
循環依賴的案例
假設例子中存在BeanA、BeanB、BeanC、BeanD四個Bean,其中
- BeanA依賴着BeanB,C
- BeanB依賴着BeanC
- BeanC依賴着BeanA
- BeanD依賴着BeanA,B,C
BeanA beanA = beanFactory.getBean("beanA",BeanA.class); BeanB beanB = beanFactory.getBean("beanB",BeanB.class); BeanC beanC = beanFactory.getBean("beanC",BeanC.class); BeanD beanD = beanFactory.getBean("beanD",BeanD.class);
代碼解析(隻保留相關代碼)
1.檢查緩存中是否已經存在執行個體化完畢的Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先檢查一級緩存中是否存在
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 如果一級緩存中不存在代表目前 Bean 還未被建立或者正在建立中
* 檢查目前 Bean 是否正處于正在建立的狀态中(當Bean建立時會将Bean名稱存放到 singletonsCurrentlyInCreation 集合中)
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//檢查二級緩存中是否存在
singletonObject = this.earlySingletonObjects.get(beanName);
/**
* @@如果二級緩存中不存在 并且 允許使用早期依賴
* allowEarlyReference : 它的含義是是否允許早期依賴
* @@那麼什麼是早期依賴?
* 就是當Bean還未成為成熟的Bean時就提前使用它,在執行個體化流程圖中我們看到在添加緩存前剛剛執行個體化Bean但是還未依賴注入時的狀态
*/
if (singletonObject == null && allowEarlyReference) {
//擷取三級緩存中的 Bean ObjectFactory
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
//如果 Bean 對應的 ObjectFactory 存在
if (singletonFactory != null) {
//使用 getObject 方法擷取到 Bean 的執行個體
singletonObject = singletonFactory.getObject();
//将 bean 從三級緩存提升至二級緩存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
2.建立Bean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
/**
* 我們一開始通過 getSingleton() 方法中擷取三級緩存中存放的Bean,這裡就是向三級緩存中添加 bean 的地方
* 流程:
* 1.檢查目前 bean 是否為單例模式,并且是否允許循環引用[講解1],并且目前是否正在建立中(在getSingleton方法中添加的)
* 2.如果允許提前曝光[講解2],addSingletonFactory() 方法向緩存中添加目前 bean 的 ObjectFactory
*
* [講解1]:目前 Bean 如果不允許循環引用(循環依賴也就是被依賴),則這裡就不會提前曝光,對應的 ObjectFactory
* 則當發生循環依賴時會抛出 BeanCreationException 異常
*
* [講解2]:提前曝光的含義就是說當 bean 還未建立完畢時就先将建立中狀态的bean放到指定緩存中,為循環依賴提供支援
*/
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
//需要提前曝光
if (earlySingletonExposure) {
/**
* 向緩存(三級緩存)中添加目前 bean 的 ObjectFactory
*/
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
/**
* Initialize the bean instance.
* 初始化 Bean 執行個體階段
*/
Object exposedObject = bean;
try {
/**
* 依賴注入這時會遞歸調用getBean
*/
populateBean(beanName, mbd, instanceWrapper);
//調用初始化方法,如:init-method
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
/**
* 當允許提前曝光時進入判斷
* @這裡做了什麼?
* 1.檢查目前bean是否經曆了一場循環依賴
* - 通過 getSingleton(beanName,false) 擷取緩存中的 bean,傳入 false 代表不擷取三級緩存中的bean
* - 為什麼說 檢查目前bean是否經曆了一場循環依賴呢? 因為上述說了傳入 false 代表不擷取三級緩存的
* - 那麼什麼情況下才會存在與一級緩存和二級緩存呢?答案就是循環依賴後 [解釋1] 和bean執行個體化完成後
* - 是以如果 getSingleton 傳回的 bean 不為空,則這個bean就是剛剛經曆了循環依賴
*
* 2.檢查提前曝光的bean和目前的Bean是否一緻
* - 下面有個判斷 if (exposedObject == bean) ,這個判斷從緩存中擷取的bean 和 經曆過初始化後的 bean
* - 是否一緻,可能我們有點暈,這裡解釋一下,緩存從的bean是什麼時候存進去的?是在 addSingletonFactory 方法(649行)
* - 然後這裡存進去的 bean 隻是提前曝光的 bean,還沒有依賴注入和初始化,但是在依賴注入和初始化時都是可能直接改變
* - 目前 bean 的執行個體的,這意味着什麼?意味着經曆了依賴注入和初始化的bean很可能和緩存中的bean就已經完全不是一個 bean了
* 下面講解當一緻或不一緻時的邏輯:
* 2.1 一緻:
* 不是很了解,直接指派,可是經曆了各種 BeanPostProsser 或者依賴注入和初始化後不是就不一樣了嗎
* 2.2 不一緻:
* 看下方對于 else if 代碼塊的解釋
*
* @[解釋1]
* 當循環依賴時,A依賴着B,B依賴着A,執行個體化A首先将A放到三級緩存中然後發現依賴着B,然後去執行個體化B,發現依賴着A
* 發現A在三級緩存,然後擷取三級緩存中的bean并且将A從三級緩存中提升到二級緩存中,執行個體化B完成,接着執行個體化A也完成。
*
* @通俗講解
* 假設我們業務上對某種資料加了緩存,假設 i 在緩存中存的值為1,當我在資料庫中把 i 的值改成 2 時,緩存中的 i 還沒有被改變還是 1
* 這時的資料已經和我們的真實資料偏離了,不一緻了,這時有兩種解決方式:1.伺服器檢查到資料不一緻抛出異常。(也就是進入else if 代碼塊)
* 2.直接使用原始值也就是1(也就是将 allowRawInjectionDespiteWrapping 改成 true),當然這兩種方式明顯不是我們正常資料庫的操作,隻是
* 為了說明目前的這個例子而已。
*
*/
if (earlySingletonExposure) {
//擷取緩存中(除三級緩存) beanName 對應的 bean
Object earlySingletonReference = getSingleton(beanName, false);
//當經曆了一場循環依賴後 earlySingletonReference 就不會為空
if (earlySingletonReference != null) {
//如果 exposedObject 沒有在初始化方法中被改變,也就是沒有被增強
if (exposedObject == bean) {
//直接指派? 可是經曆了各種 BeanPostProsser 或者依賴注入和初始化後不是就不一樣了嗎
exposedObject = earlySingletonReference;
}
/**
*
* 走到 else if 時說明 目前 Bean 被 BeanPostProessor 增強了
* 判斷的條件為:
* 1.如果允許使用被增強的
* 2.檢查是否存在依賴目前bean的bean
*
* 如果存在依賴的bean已經被執行個體化完成的,如果存在則抛出異常
* 為什麼抛出異常呢?
* 因為依賴目前bean 的bean 已經在内部注入了目前bean的舊版本,但是通過初始化方法後這個bean的版本已經變成新的了
* 舊的哪個已經不适用了,是以抛出異常
*
*/
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set 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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
try {
/**
* Register bean as disposable.
* 注冊 Bean 的銷毀方法拓展
*/
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}