Spring — 循環依賴

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

  • 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 獲取單例流程(一) 中咱們曾說起過三級緩存this

@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 這個 Mapurl

咱們能夠先梳理一下總體的流程spa

beanA --> beanB --> beanC -->beanA
複製代碼

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

  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 的循環依賴3d

  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 在一個構造函數依賴的關係中,參數不全,再牛皮也不能把

此次必定?
此次必定?
羣聊
羣聊

本文使用 mdnice 排版

相關文章
相關標籤/搜索