Spring — 循環依賴

讀完這篇文章你將會收穫到

  • Spring循環依賴可以分爲哪兩種
  • Spring如何解決 setter循環依賴
  • Spring爲何是三級緩存 , 二級不行 ?
  • Spring爲啥不能解決構造器循環依賴

概述

循環依賴就是循環引用,兩個或以上的 bean相互持有對方。比如說 beanA引用 beanB, beanB引用 beanCbeanC引用 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

以上面爲例子、我們先假設它們是構造器的循環依賴

  1. Spring初始化完成之後、接收到一個 getBean的調用請求、請求 beanA
  2. Spring發現三級緩存中都沒有 beanA的存在、所以開始創建 beanA的流程
  3. beanA放入到 singletonsCurrentlyInCreation集合中去、代表着 beanA正在創建中
  4. 兜兜轉轉,發現我要 new一個 beanA的對象、我要先獲得一個 beanB的對象、好、我們就進行一個 getBean(beanB)
  5. Spring發現三級緩存中都沒有 beanB的存在、所以開始創建 beanB的流程
  6. beanB放入到 singletonsCurrentlyInCreation集合中去、代表着 beanB正在創建中
  7. 兜兜轉轉,發現我要 new一個 beanB的對象、我要先獲得一個 beanC的對象、好、我們就進行一個 getBean(beanC)
  8. Spring發現三級緩存中都沒有 beanC的存在、所以開始創建 beanC的流程
  9. beanC放入到 singletonsCurrentlyInCreation集合中去、代表着 beanC正在創建中
  10. 兜兜轉轉,發現我要 new一個 beanC的對象、我要先獲得一個 beanA的對象、好、我們就進行一個 getBean(beanA)
  11. Spring發現三級緩存中都沒有 beanA的存在、所以開始創建 beanA的流程
  12. beanA放入到 singletonsCurrentlyInCreation集合中去、但是在這個時候、插入到集合中失敗、直接拋出異常

而假如我們是一個 setter的循環依賴

  1. Spring初始化完成之後、接收到一個 getBean的調用請求、請求 beanA
  2. 先判斷三級緩存中有沒有 beanA,如果沒有則往下進行
  3. beanA放入到 singletonsCurrentlyInCreation集合中去、代表着 beanA正在創建中
  4. 兜兜轉轉,終於創建了一個 beanA, 但是這個時候的 beanA是一個不完整的狀態、因爲很多屬性沒有被賦值、比如說 beanA中的成員變量 beanB現在還是一個 null的狀態
  5. 然後判斷是否需要將當前創建的不完整的 beanA加入到第三級緩存中,正常來說都是會被加入到第三級緩存中的
  6. 加入第三級緩存以後、進行一個屬性填充,這個時候發現需要填充一個 beanB對象
  7. 然後如上面那樣、先看看三級緩存有沒有 beanB,如果沒有則創建一個並不完整的 beanB、然後加入到第三級緩存中、然後發現需要填充一個 beanC的屬性
  8. 然後如上面那樣、先看看三級緩存有沒有 beanC,如果沒有則創建一個並不完整的 beanC、然後加入到第三級緩存中、然後發現需要填充一個 beanA的屬性
  9. 這個時候,先看看三級緩存中有沒有 beanA,發現在第三級緩衝中有不完整的 beanA、將其從第三級緩存中移除出來、放入到第二級緩存中,然後返回給 beanC用於填充屬性
  10. 然後 beanC的 屬性填充完畢,則將其從 singletonsCurrentlyInCreation集合中移除掉,代表 beanC已經真正的創建好了
  11. 然後將 beanC加入到第一級緩存中,並將其從第三級緩存中移除,並返回給 beanBbeanB也如 beanC那樣處理
  12. beanA也如 beanBbeanC那樣處理、加入到第一級緩存中、然後從第二級緩存中移除
  13. 結束

其實上面的屁話又長又臭,但是流程還是非常簡單的

爲啥是三級緩存,二級不行嗎?

/**
 * 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的時候、要麼就直接調用 ObjectFactorygetObject方法獲取經過回調的 bean放入到第二級緩存(不管這個 bean存不存在一個循環引用的關係鏈中),要麼就直接放剛剛創建好的沒有完成初始化的 bean放入到第二級緩存。無論是哪種情況,都無法達到這樣一個需求:當存在循環依賴的時候,我們作爲用戶需要對其進行一些設置或者一些其他的操作

爲啥不能解決構造函數的循環依賴

如果按照解決 setter循環依賴的流程、是否能夠解決?先將一個不完整的 bean放入到第三級緩存中,然後提供出去給其他 bean依賴。但是呢,問題是我無法創建出這麼一個不完整的 bean在一個構造函數依賴的關係中,參數不全,再牛皮也不能把

這次一定?

羣聊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章