精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

爛了大街的 Spring 循環依賴問題,你以為自己就真會了嗎

開發 前端
初學 Spring 的時候,我們就知道 IOC,控制反轉么,它將原本在程序中手動創建對象的控制權,交由 Spring 框架來管理,不需要我們手動去各種 new XXX。

[[340562]]

本文轉載自微信公眾號「JavaKeeper」,作者海星 。轉載本文請聯系JavaKeeper公眾號。  

前言

循環依賴問題,算是一道爛大街的面試題了,解毒之前,我們先來回顧兩個知識點:

初學 Spring 的時候,我們就知道 IOC,控制反轉么,它將原本在程序中手動創建對象的控制權,交由 Spring 框架來管理,不需要我們手動去各種 new XXX。

盡管是 Spring 管理,不也得創建對象嗎, Java 對象的創建步驟很多,可以 new XXX、序列化、clone() 等等, 只是 Spring 是通過反射 + 工廠的方式創建對象并放在容器的,創建好的對象我們一般還會對對象屬性進行賦值,才去使用,可以理解是分了兩個步驟。

好了,對這兩個步驟有個印象就行,接著我們進入循環依賴,先說下循環依賴的概念

什么是循環依賴

所謂的循環依賴是指,A 依賴 B,B 又依賴 A,它們之間形成了循環依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A,形成了循環依賴。更或者是自己依賴自己。它們之間的依賴關系如下:

 

這里以兩個類直接相互依賴為例,他們的實現代碼可能如下:

  1. public class BeanB { 
  2.     private BeanA beanA; 
  3.     public void setBeanA(BeanA beanA) { 
  4.   this.beanA = beanA; 
  5.  } 
  6.  
  7. public class BeanA { 
  8.     private BeanB beanB; 
  9.     public void setBeanB(BeanB beanB) { 
  10.         this.beanB = beanB; 
  11.  } 

配置信息如下(用注解方式注入同理,只是為了方便理解,用了配置文件):

  1. <bean id="beanA" class="priv.starfish.BeanA"
  2.   <property name="beanB" ref="beanB"/> 
  3. </bean> 
  4.  
  5. <bean id="beanB" class="priv.starfish.BeanB"
  6.   <property name="beanA" ref="beanA"/> 
  7. </bean> 

Spring 啟動后,讀取如上的配置文件,會按順序先實例化 A,但是創建的時候又發現它依賴了 B,接著就去實例化 B ,同樣又發現它依賴了 A ,這尼瑪咋整?無限循環呀

Spring “肯定”不會讓這種事情發生的,如前言我們說的 Spring 實例化對象分兩步,第一步會先創建一個原始對象,只是沒有設置屬性,可以理解為"半成品"—— 官方叫 A 對象的早期引用(EarlyBeanReference),所以當實例化 B 的時候發現依賴了 A, B 就會把這個“半成品”設置進去先完成實例化,既然 B 完成了實例化,所以 A 就可以獲得 B 的引用,也完成實例化了,這其實就是 Spring 解決循環依賴的思想。

 

不理解沒關系,先有個大概的印象,然后我們從源碼來看下 Spring 具體是怎么解決的。

源碼解毒

代碼版本:5.0.16.RELEASE

在 Spring IOC 容器讀取 Bean 配置創建 Bean 實例之前, 必須對它進行實例化。只有在容器實例化后,才可以從 IOC 容器里獲取 Bean 實例并使用,循環依賴問題也就是發生在實例化 Bean 的過程中的,所以我們先回顧下獲取 Bean 的過程。

獲取 Bean 流程

Spring IOC 容器中獲取 bean 實例的簡化版流程如下(排除了各種包裝和檢查的過程)

 

大概的流程順序(可以結合著源碼看下,我就不貼了,貼太多的話,嘔~嘔嘔,想吐):

流程從 getBean 方法開始,getBean 是個空殼方法,所有邏輯直接到 doGetBean 方法中

transformedBeanName 將 name 轉換為真正的 beanName(name 可能是 FactoryBean 以 & 字符開頭或者有別名的情況,所以需要轉化下)

然后通過 getSingleton(beanName) 方法嘗試從緩存中查找是不是有該實例 sharedInstance(單例在 Spring 的同一容器只會被創建一次,后續再獲取 bean,就直接從緩存獲取即可)

如果有的話,sharedInstance 可能是完全實例化好的 bean,也可能是一個原始的 bean,所以再經 getObjectForBeanInstance 處理即可返回

當然 sharedInstance 也可能是 null,這時候就會執行創建 bean 的邏輯,將結果返回

第三步的時候我們提到了一個緩存的概念,這個就是 Spring 為了解決單例的循環依賴問題而設計的 三級緩存

  1. /** Cache of singleton objects: bean name --> bean instance */ 
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); 
  3.  
  4. /** Cache of singleton factories: bean name --> ObjectFactory */ 
  5. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); 
  6.  
  7. /** Cache of early singleton objects: bean name --> bean instance */ 
  8. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); 

這三級緩存的作用分別是:

  • singletonObjects:完成初始化的單例對象的 cache,這里的 bean 經歷過 實例化->屬性填充->初始化 以及各種后置處理(一級緩存)
  • earlySingletonObjects:存放原始的 bean 對象(完成實例化但是尚未填充屬性和初始化),僅僅能作為指針提前曝光,被其他 bean 所引用,用于解決循環依賴的 (二級緩存)
  • singletonFactories:在 bean 實例化完之后,屬性填充以及初始化之前,如果允許提前曝光,Spring 會將實例化后的 bean 提前曝光,也就是把該 bean 轉換成beanFactory 并加入到 singletonFactories(三級緩存)

我們首先從緩存中試著獲取 bean,就是從這三級緩存中查找

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
  2.     // 從 singletonObjects 獲取實例,singletonObjects 中的實例都是準備好的 bean 實例,可以直接使用 
  3.     Object singletonObject = this.singletonObjects.get(beanName); 
  4.     //isSingletonCurrentlyInCreation() 判斷當前單例bean是否正在創建中 
  5.     if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
  6.         synchronized (this.singletonObjects) { 
  7.             // 一級緩存沒有,就去二級緩存找 
  8.             singletonObject = this.earlySingletonObjects.get(beanName); 
  9.             if (singletonObject == null && allowEarlyReference) { 
  10.                 // 二級緩存也沒有,就去三級緩存找 
  11.                 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
  12.                 if (singletonFactory != null) { 
  13.                     // 三級緩存有的話,就把他移動到二級緩存,.getObject() 后續會講到 
  14.                     singletonObject = singletonFactory.getObject(); 
  15.                     this.earlySingletonObjects.put(beanName, singletonObject); 
  16.                     this.singletonFactories.remove(beanName); 
  17.                 } 
  18.             } 
  19.         } 
  20.     } 
  21.     return singletonObject; 

如果緩存沒有的話,我們就要創建了,接著我們以單例對象為例,再看下創建 bean 的邏輯(大括號表示內部類調用方法):

 

 

1.創建 bean 從以下代碼開始,一個匿名內部類方法參數(總覺得 Lambda 的方式可讀性不如內部類好理解)

  1. if (mbd.isSingleton()) { 
  2.     sharedInstance = getSingleton(beanName, () -> { 
  3.         try { 
  4.             return createBean(beanName, mbd, args); 
  5.         } 
  6.         catch (BeansException ex) { 
  7.             destroySingleton(beanName); 
  8.             throw ex; 
  9.         } 
  10.     }); 
  11.     bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 

getSingleton() 方法內部主要有兩個方法

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { 
  2.     // 創建 singletonObject 
  3.  singletonObject = singletonFactory.getObject(); 
  4.     // 將 singletonObject 放入緩存 
  5.     addSingleton(beanName, singletonObject); 

2.getObject() 匿名內部類的實現真正調用的又是 createBean(beanName, mbd, args)

3.往里走,主要的實現邏輯在 doCreateBean方法,先通過 createBeanInstance 創建一個原始 bean 對象

4.接著 addSingletonFactory 添加 bean 工廠對象到 singletonFactories 緩存(三級緩存)

5.通過 populateBean 方法向原始 bean 對象中填充屬性,并解析依賴,假設這時候創建 A 之后填充屬性時發現依賴 B,然后創建依賴對象 B 的時候又發現依賴 A,還是同樣的流程,又去 getBean(A),這個時候三級緩存已經有了 beanA 的“半成品”,這時就可以把 A 對象的原始引用注入 B 對象(并將其移動到二級緩存)來解決循環依賴問題。這時候 getObject() 方法就算執行結束了,返回完全實例化的 bean

6.最后調用 addSingleton 把完全實例化好的 bean 對象放入 singletonObjects 緩存(一級緩存)中,打完收工

Spring 解決循環依賴

建議搭配著“源碼”看下邊的邏輯圖,更好下飯

 

流程其實上邊都已經說過了,結合著上圖我們再看下具體細節,用大白話再捋一捋:

  1. Spring 創建 bean 主要分為兩個步驟,創建原始 bean 對象,接著去填充對象屬性和初始化
  2. 每次創建 bean 之前,我們都會從緩存中查下有沒有該 bean,因為是單例,只能有一個
  3. 當我們創建 beanA 的原始對象后,并把它放到三級緩存中,接下來就該填充對象屬性了,這時候發現依賴了 beanB,接著就又去創建 beanB,同樣的流程,創建完 beanB 填充屬性時又發現它依賴了 beanA,又是同樣的流程,不同的是,這時候可以在三級緩存中查到剛放進去的原始對象 beanA,所以不需要繼續創建,用它注入 beanB,完成 beanB 的創建
  4. 既然 beanB 創建好了,所以 beanA 就可以完成填充屬性的步驟了,接著執行剩下的邏輯,閉環完成

這就是單例模式下 Spring 解決循環依賴的流程了。

但是這個地方,不管是誰看源碼都會有個小疑惑,為什么需要三級緩存呢,我趕腳二級他也夠了呀

革命尚未成功,同志仍需努力

跟源碼的時候,發現在創建 beanB 需要引用 beanA 這個“半成品”的時候,就會觸發"前期引用",即如下代碼:

  1. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
  2. if (singletonFactory != null) { 
  3.     // 三級緩存有的話,就把他移動到二級緩存 
  4.     singletonObject = singletonFactory.getObject(); 
  5.     this.earlySingletonObjects.put(beanName, singletonObject); 
  6.     this.singletonFactories.remove(beanName); 

singletonFactory.getObject() 是一個接口方法,這里具體的實現方法在

  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { 
  2.     Object exposedObject = bean; 
  3.     if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { 
  4.         for (BeanPostProcessor bp : getBeanPostProcessors()) { 
  5.             if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { 
  6.                 SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; 
  7.                 // 這么一大段就這句話是核心,也就是當bean要進行提前曝光時, 
  8.                 // 給一個機會,通過重寫后置處理器的getEarlyBeanReference方法,來自定義操作bean 
  9.                 // 值得注意的是,如果提前曝光了,但是沒有被提前引用,則該后置處理器并不生效!!! 
  10.                 // 這也正式三級緩存存在的意義,否則二級緩存就可以解決循環依賴的問題 
  11.                 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); 
  12.             } 
  13.         } 
  14.     } 
  15.     return exposedObject; 

這個方法就是 Spring 為什么使用三級緩存,而不是二級緩存的原因,它的目的是為了后置處理,如果沒有 AOP 后置處理,就不會走進 if 語句,直接返回了 exposedObject ,相當于啥都沒干,二級緩存就夠用了。

所以又得出結論,這個三級緩存應該和 AOP 有關系,繼續。

在 Spring 的源碼中getEarlyBeanReference 是 SmartInstantiationAwareBeanPostProcessor接口的默認方法,真正實現這個方法的只有**AbstractAutoProxyCreator** 這個類,用于提前曝光的 AOP 代理。

  1. @Override 
  2. public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { 
  3.    Object cacheKey = getCacheKey(bean.getClass(), beanName); 
  4.    this.earlyProxyReferences.put(cacheKey, bean); 
  5.    // 對bean進行提前Spring AOP代理 
  6.    return wrapIfNecessary(bean, beanName, cacheKey); 

這么說有點干,來個小 demo 吧,我們都知道 Spring AOP、事務等都是通過代理對象來實現的,而事務的代理對象是由自動代理創建器來自動完成的。也就是說 Spring 最終給我們放進容器里面的是一個代理對象,而非原始對象,假設我們有如下一段業務代碼:

  1. @Service 
  2. public class HelloServiceImpl implements HelloService { 
  3.    @Autowired 
  4.    private HelloService helloService; 
  5.  
  6.    @Override 
  7.    @Transactional 
  8.    public Object hello() { 
  9.       return "Hello JavaKeeper"
  10.    } 

此 Service 類使用到了事務,所以最終會生成一個 JDK 動態代理對象 Proxy。剛好它又存在自己引用自己的循環依賴,完美符合我們的場景需求。

我們再自定義一個后置處理,來看下效果:

  1. @Component 
  2. public class HelloProcessor implements SmartInstantiationAwareBeanPostProcessor { 
  3.  
  4.  @Override 
  5.  public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { 
  6.   System.out.println("提前曝光了:"+beanName); 
  7.   return bean; 
  8.  } 

可以看到,調用方法棧中有我們自己實現的 HelloProcessor,說明這個 bean 會通過 AOP 代理處理。

 

再從源碼看下這個自己循環自己的 bean 的創建流程:

  1. protected Object doCreateBean( ... ){ 
  2.  ... 
  3.   
  4.  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); 
  5.     // 需要提前暴露(支持循環依賴),就注冊一個ObjectFactory到三級緩存 
  6.  if (earlySingletonExposure) {  
  7.         // 添加 bean 工廠對象到 singletonFactories 緩存中,并獲取原始對象的早期引用 
  8.   //匿名內部方法 getEarlyBeanReference 就是后置處理器  
  9.   // SmartInstantiationAwareBeanPostProcessor 的一個方法, 
  10.   // 它的功效為:保證自己被循環依賴的時候,即使被別的Bean @Autowire進去的也是代理對象 
  11.   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 
  12.  } 
  13.  
  14.  // 此處注意:如果此處自己被循環依賴了  那它會走上面的getEarlyBeanReference,從而創建一個代理對象從  三級緩存轉移到二級緩存里 
  15.  // 注意此時候對象還在二級緩存里,并沒有在一級緩存。并且此時后續的這兩步操作還是用的 exposedObject,它仍舊是原始對象~~~ 
  16.  populateBean(beanName, mbd, instanceWrapper); 
  17.  exposedObject = initializeBean(beanName, exposedObject, mbd); 
  18.  
  19.  // 因為事務的AOP自動代理創建器在getEarlyBeanReference 創建代理后,initializeBean 就不會再重復創建了,二選一的) 
  20.       
  21.  // 所以經過這兩大步后,exposedObject 還是原始對象,通過 getEarlyBeanReference 創建的代理對象還在三級緩存呢 
  22.   
  23.  ... 
  24.   
  25.  // 循環依賴校驗 
  26.  if (earlySingletonExposure) { 
  27.         // 注意此處第二個參數傳的false,表示不去三級緩存里再去調用一次getObject()方法了~~~,此時代理對象還在二級緩存,所以這里拿出來的就是個 代理對象 
  28.   // 最后賦值給exposedObject  然后return出去,進而最終被addSingleton()添加進一級緩存里面去   
  29.   // 這樣就保證了我們容器里 最終實際上是代理對象,而非原始對象~~~~~ 
  30.   Object earlySingletonReference = getSingleton(beanName, false); 
  31.   if (earlySingletonReference != null) { 
  32.    if (exposedObject == bean) {  
  33.     exposedObject = earlySingletonReference; 
  34.    } 
  35.   } 
  36.   ... 
  37.  } 
  38.   

自我解惑:

問:還是不太懂,為什么這么設計呢,即使有代理,在二級緩存代理也可以吧 | 為什么要使用三級緩存呢?

我們再來看下相關代碼,假設我們現在是二級緩存架構,創建 A 的時候,我們不知道有沒有循環依賴,所以放入二級緩存提前暴露,接著創建 B,也是放入二級緩存,這時候發現又循環依賴了 A,就去二級緩存找,是有,但是如果此時還有 AOP 代理呢,我們要的是代理對象可不是原始對象,這怎么辦,只能改邏輯,在第一步的時候,不管3721,所有 Bean 統統去完成 AOP 代理,如果是這樣的話,就不需要三級緩存了,但是這樣不僅沒有必要,而且違背了 Spring 在結合 AOP 跟 Bean 的生命周期的設計。

所以 Spring “多此一舉”的將實例先封裝到 ObjectFactory 中(三級緩存),主要關鍵點在 getObject() 方法并非直接返回實例,而是對實例又使用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法對 bean 進行處理,也就是說,當 Spring 中存在該后置處理器,所有的單例 bean 在實例化后都會被進行提前曝光到三級緩存中,但是并不是所有的 bean 都存在循環依賴,也就是三級緩存到二級緩存的步驟不一定都會被執行,有可能曝光后直接創建完成,沒被提前引用過,就直接被加入到一級緩存中。因此可以確保只有提前曝光且被引用的 bean 才會進行該后置處理。

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
  2.     Object singletonObject = this.singletonObjects.get(beanName); 
  3.     if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
  4.         synchronized (this.singletonObjects) { 
  5.             singletonObject = this.earlySingletonObjects.get(beanName); 
  6.             if (singletonObject == null && allowEarlyReference) { 
  7.              // 三級緩存獲取,key=beanName value=objectFactory,objectFactory中存儲     //getObject()方法用于獲取提前曝光的實例 
  8.                 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
  9.                 if (singletonFactory != null) { 
  10.                     // 三級緩存有的話,就把他移動到二級緩存 
  11.                     singletonObject = singletonFactory.getObject(); 
  12.                     this.earlySingletonObjects.put(beanName, singletonObject); 
  13.                     this.singletonFactories.remove(beanName); 
  14.                 } 
  15.             } 
  16.         } 
  17.     } 
  18.     return singletonObject; 
  19.  
  20.  
  21. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && 
  22.       isSingletonCurrentlyInCreation(beanName)); 
  23. if (earlySingletonExposure) { 
  24.    if (logger.isDebugEnabled()) { 
  25.       logger.debug("Eagerly caching bean '" + beanName + 
  26.             "' to allow for resolving potential circular references"); 
  27.    } 
  28.    // 添加 bean 工廠對象到 singletonFactories 緩存中,并獲取原始對象的早期引用 
  29.    //匿名內部方法 getEarlyBeanReference 就是后置處理器 
  30.    // SmartInstantiationAwareBeanPostProcessor 的一個方法, 
  31.    // 它的功效為:保證自己被循環依賴的時候,即使被別的Bean @Autowire進去的也是代理對象~~~~  AOP自動代理創建器此方法里會創建的代理對象~~~ 
  32.    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 

再問:AOP 代理對象提前放入了三級緩存,沒有經過屬性填充和初始化,這個代理又是如何保證依賴屬性的注入的呢?

這個又涉及到了 Spring 中動態代理的實現,不管是cglib代理還是jdk動態代理生成的代理類,代理時,會將目標對象 target 保存在最后生成的代理 $proxy 中,當調用 $proxy 方法時會回調 h.invoke,而 h.invoke 又會回調目標對象 target 的原始方法。所有,其實在 AOP 動態代理時,原始 bean 已經被保存在 提前曝光代理中了,之后 原始 bean 繼續完成屬性填充和初始化操作。因為 AOP 代理$proxy中保存著 traget 也就是是 原始bean 的引用,因此后續 原始bean 的完善,也就相當于Spring AOP中的 target 的完善,這樣就保證了 AOP 的屬性填充與初始化了!

非單例循環依賴

看完了單例模式的循環依賴,我們再看下非單例的情況,假設我們的配置文件是這樣的:

  1. <bean id="beanA" class="priv.starfish.BeanA" scope="prototype"
  2.    <property name="beanB" ref="beanB"/> 
  3. </bean> 
  4.  
  5. <bean id="beanB" class="priv.starfish.BeanB" scope="prototype"
  6.    <property name="beanA" ref="beanA"/> 
  7. </bean> 

啟動 Spring,結果如下:

  1. Error creating bean with name 'beanA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'
  2.  
  3. Error creating bean with name 'beanB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'
  4.  
  5. Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference? 

對于 prototype 作用域的 bean,Spring 容器無法完成依賴注入,因為 Spring 容器不進行緩存 prototype 作用域的 bean ,因此無法提前暴露一個創建中的bean 。

原因也挺好理解的,原型模式每次請求都會創建一個實例對象,即使加了緩存,循環引用太多的話,就比較麻煩了就,所以 Spring 不支持這種方式,直接拋出異常:

  1. if (isPrototypeCurrentlyInCreation(beanName)) { 
  2.    throw new BeanCurrentlyInCreationException(beanName); 

構造器循環依賴

上文我們講的是通過 Setter 方法注入的單例 bean 的循環依賴問題,用 Spring 的小伙伴也都知道,依賴注入的方式還有構造器注入、工廠方法注入的方式(很少使用),那如果構造器注入方式也有循環依賴,可以搞不?

我們再改下代碼和配置文件

  1. public class BeanA { 
  2.    private BeanB beanB; 
  3.    public BeanA(BeanB beanB) { 
  4.       this.beanB = beanB; 
  5.    } 
  6.  
  7. public class BeanB { 
  8.  private BeanA beanA; 
  9.  public BeanB(BeanA beanA) { 
  10.   this.beanA = beanA; 
  11.  } 
  1. <bean id="beanA" class="priv.starfish.BeanA"
  2. <constructor-arg ref="beanB"/> 
  3. </bean> 
  4.  
  5. <bean id="beanB" class="priv.starfish.BeanB"
  6. <constructor-arg ref="beanA"/> 
  7. </bean> 

執行結果,又是異常

 

看看官方給出的說法

Circular dependencies

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

大概意思是:

如果您主要使用構造器注入,循環依賴場景是無法解決的。建議你用 setter 注入方式代替構造器注入

其實也不是說只要是構造器注入就會有循環依賴問題,Spring 在創建 Bean 的時候默認是按照自然排序來進行創建的,我們暫且把先創建的 bean 叫主 bean,上文的 A 即主 bean,只要主 bean 注入依賴 bean 的方式是 setter 方式,依賴 bean 的注入方式無所謂,都可以解決,反之亦然

所以上文我們 AB 循環依賴問題,只要 A 的注入方式是 setter ,就不會有循環依賴問題。

面試官問:為什么呢?

Spring 解決循環依賴依靠的是 Bean 的“中間態”這個概念,而這個中間態指的是已經實例化,但還沒初始化的狀態。實例化的過程又是通過構造器創建的,如果 A 還沒創建好出來,怎么可能提前曝光,所以構造器的循環依賴無法解決,我一直認為應該先有雞才能有蛋。

小總結 | 面試這么答B 中提前注入了一個沒有經過初始化的 A 類型對象不會有問題嗎?

雖然在創建 B 時會提前給 B 注入了一個還未初始化的 A 對象,但是在創建 A 的流程中一直使用的是注入到 B 中的 A 對象的引用,之后會根據這個引用對 A 進行初始化,所以這是沒有問題的。

Spring 是如何解決的循環依賴?

Spring 為了解決單例的循環依賴問題,使用了三級緩存。其中一級緩存為單例池(singletonObjects),二級緩存為提前曝光對象(earlySingletonObjects),三級緩存為提前曝光對象工廠(singletonFactories)。

假設A、B循環引用,實例化 A 的時候就將其放入三級緩存中,接著填充屬性的時候,發現依賴了 B,同樣的流程也是實例化后放入三級緩存,接著去填充屬性時又發現自己依賴 A,這時候從緩存中查找到早期暴露的 A,沒有 AOP 代理的話,直接將 A 的原始對象注入 B,完成 B 的初始化后,進行屬性填充和初始化,這時候 B 完成后,就去完成剩下的 A 的步驟,如果有 AOP 代理,就進行 AOP 處理獲取代理后的對象 A,注入 B,走剩下的流程。

為什么要使用三級緩存呢?二級緩存能解決循環依賴嗎?

如果沒有 AOP 代理,二級緩存可以解決問題,但是有 AOP 代理的情況下,只用二級緩存就意味著所有 Bean 在實例化后就要完成 AOP 代理,這樣違背了 Spring 設計的原則,Spring 在設計之初就是通過 AnnotationAwareAspectJAutoProxyCreator 這個后置處理器來在 Bean 生命周期的最后一步來完成 AOP 代理,而不是在實例化后就立馬進行 AOP 代理。

參考與感謝:

《Spring 源碼深度解析》- 郝佳著

https://developer.aliyun.com/article/766880

http://www.tianxiaobo.com/2018/06/08/Spring-IOC-容器源碼分析-循環依賴的解決辦法

https://cloud.tencent.com/developer/article/1497692

 

https://blog.csdn.net/chaitoudaren/article/details/105060882

 

責任編輯:武曉燕 來源: JavaKeeper
相關推薦

2021-02-08 11:20:27

Java類型數組

2022-03-11 14:59:21

JavaScript數組字符串

2024-11-29 08:53:46

2023-12-12 08:02:10

2022-04-26 08:41:54

JDK動態代理方法

2022-11-09 07:20:43

調用日志502報錯nginx

2023-05-04 08:06:27

Spring循環依賴

2024-03-04 07:41:18

SpringAOPOOP?

2024-03-18 08:06:59

JavaGo開發

2023-06-05 08:36:04

SQL函數RANK()

2022-07-11 09:00:37

依賴配置文件Mybati

2023-05-05 08:29:15

Spring后臺服務器

2022-03-03 07:34:31

注解容器作用域

2023-06-27 07:21:51

前端開發坑點

2024-09-30 09:05:46

Linux網絡延遲

2024-08-27 11:00:56

單例池緩存bean

2022-07-08 09:27:48

CSSIFC模型

2024-02-02 11:03:11

React數據Ref

2024-01-02 07:04:23

2022-07-08 08:17:48

Spring接口配置
點贊
收藏

51CTO技術棧公眾號

成年人免费看毛片| 又色又爽又黄18网站| 69久久精品| 日本美女一区二区三区视频| 最近中文字幕mv在线一区二区三区四区| 久久九九国产视频| 欧美另类极品| 波多野结衣在线aⅴ中文字幕不卡| 欧美中文在线观看| 无码黑人精品一区二区| 欧美变态网站| 欧美男人的天堂一二区| 每日在线观看av| 超碰在线影院| av午夜一区麻豆| 成人免费高清完整版在线观看| 久久精品性爱视频| 91欧美在线| 日韩精品视频在线观看网址| 亚洲黄色av片| 成人勉费视频| 亚洲国产日韩一区二区| 天堂精品视频| 图片区 小说区 区 亚洲五月| 蜜臀91精品一区二区三区| 久久全球大尺度高清视频| 欧日韩不卡视频| 亚洲欧美校园春色| 精品国免费一区二区三区| 成年网站在线播放| 亚洲天堂av影院| 一区二区不卡在线视频 午夜欧美不卡在| 日本一区高清不卡| 天天操天天操天天| 国产乱国产乱300精品| 国产精品综合网站| 最近免费中文字幕大全免费版视频| 欧美日韩亚洲国产精品| 久久精品最新地址| 在线观看免费黄色网址| 亚洲警察之高压线| 日韩成人在线观看| 手机免费看av片| 一区二区三区四区视频免费观看 | 日韩少妇裸体做爰视频| 午夜精品av| 久久偷看各类女兵18女厕嘘嘘| 先锋影音av在线| 国产成人高清| 亚洲人午夜色婷婷| 中文字幕丰满孑伦无码专区| 久久夜色精品国产噜噜av小说| 欧美成人精品福利| 绯色av蜜臀vs少妇| 伊人久久大香线蕉av超碰| 日韩午夜精品电影| 亚洲AV无码久久精品国产一区| 一区二区三区| 91精品国产全国免费观看| 日本高清久久久| 天天综合在线观看| 欧美一区二区二区| 91精产国品一二三| 久久国产精品色av免费看| 精品国产凹凸成av人导航| 丰满熟女人妻一区二区三区| 99re8这里有精品热视频免费| 精品国产髙清在线看国产毛片| 无套白嫩进入乌克兰美女| 日韩一级淫片| 亚洲精品久久久久久久久久久久| 五级黄高潮片90分钟视频| 在线亚洲a色| 国产亚洲精品综合一区91| 国产又黄又粗视频| 国产精品麻豆久久| 欧美黑人性猛交| 精品国产乱码一区二区| 久久亚洲色图| 91精品久久久久久久久中文字幕 | 成人av网站大全| 久久99国产精品| av中文资源在线| 亚洲欧美国产高清| 国产乱子伦农村叉叉叉| 欧美韩国亚洲| 欧美一级高清大全免费观看| 午夜视频在线观看国产| 精品国产精品国产偷麻豆| 久久精品电影网站| 免费日韩一级片| 男女男精品视频网| eeuss一区二区三区| 青青青草网站免费视频在线观看| 欧美国产精品一区二区三区| 樱空桃在线播放| 第一福利在线视频| 欧美日韩在线播放三区| 性猛交╳xxx乱大交| 九色精品91| 欧美理论电影在线观看| 日日摸天天添天天添破| 国产真实乱对白精彩久久| 国产综合 伊人色| 日本中文字幕在线看| 亚洲综合色在线| 天天综合网日韩| 日本欧美韩国国产| 欧美床上激情在线观看| 秋霞av一区二区三区| 国产福利一区二区| 午夜精品美女久久久久av福利| 美女尤物在线视频| 欧美日韩精品久久久| 中文字幕乱码一区| 久久精品青草| 国产精品91在线| 亚洲日本在线播放| 亚洲综合在线五月| 四季av一区二区三区| 西野翔中文久久精品国产| 日韩视频在线免费| 国产无遮挡又黄又爽又色视频| 国产成人福利片| 一区二区三区国产福利| 欧美专区福利免费| 亚洲精品国产精品国产自| 国产麻豆视频在线观看| 亚洲一区日本| 国产中文一区二区| av有码在线观看| 欧美成人a∨高清免费观看| 欧美巨胸大乳hitomi| 久久综合图片| 久久久久久一区| free性m.freesex欧美| 欧美一级久久久久久久大片| 林心如三级全黄裸体| 久久精品首页| 另类小说综合网| 日韩深夜视频| 日韩精品在线影院| 日韩人妻无码一区二区三区99| 岛国精品在线观看| 成年丰满熟妇午夜免费视频| 成人在线视频国产| 久久精品视频一| 国产精品国产三级国产aⅴ| 国产精品乱码一区二三区小蝌蚪| 国产天堂在线播放| 成人三级视频| 国产精品丝袜久久久久久不卡| 国产高清视频在线观看| 欧美在线综合视频| 夫妇露脸对白88av| 麻豆精品一区二区综合av| 一区二区三区四区国产| 四虎国产精品免费久久| 久久国产精品影片| 丰满少妇被猛烈进入| 亚洲成人av免费| 亚洲第一黄色网址| 日韩精品免费视频人成| 亚洲激情电影在线| 国产精品视频一区二区三区综合 | 阿v视频在线| 国产视频精品xxxx| 波多野结衣电车痴汉| 亚洲国产精品传媒在线观看| 色天使在线观看| 欧美/亚洲一区| 国新精品乱码一区二区三区18 | 国产精品亚洲аv天堂网| 韩国福利在线| 69堂国产成人免费视频| 欧美久久久久久久久久久久| 成人爽a毛片一区二区免费| 日韩精品视频一区二区在线观看| 精品国产一区二区三区久久久樱花| 国产精品一区二区三区久久| 在线看福利影| 精品无人区乱码1区2区3区在线| 日韩乱码一区二区三区| 亚洲女女做受ⅹxx高潮| 国产二级一片内射视频播放 | 亚洲成人av电影| 少妇人妻好深好紧精品无码| 国产乱对白刺激视频不卡| 国产欧美在线一区| 久久精品亚洲人成影院| 精品国产乱码久久久久久108| 日本精品在线一区| 欧美日韩国产成人在线观看| 日本国产在线| 日韩一区二区三区观看| 九九九在线观看| 亚洲色图欧美偷拍| 成人影视免费观看| 国产一区激情在线| 国产性xxxx18免费观看视频| 亚洲成人一区| 日韩电影在线播放| 永久免费精品视频| 国产日韩精品在线观看| 少妇视频在线观看| 久久6免费高清热精品| 精品av中文字幕在线毛片| 日韩免费性生活视频播放| 懂色av蜜臀av粉嫩av分享吧最新章节| 亚洲欧美另类久久久精品| 久久亚洲AV无码专区成人国产| 国产suv精品一区二区三区| 久久国产成人精品国产成人亚洲| 亚洲乱码免费伦视频| 精品一卡二卡三卡四卡日本乱码| 91精品福利观看| 国产成人激情小视频| av资源中文在线天堂| 九九九久久久久久| 日本不卡在线| 国产亚洲欧美日韩精品| 午夜性色福利影院| 欧美xxx久久| 国产女人18毛片水18精| 在线观看免费一区| 亚洲va在线观看| 亚洲成av人综合在线观看| 国产1区2区3区4区| 综合激情成人伊人| 免费看黄色三级| 久久精品视频一区| 国产美女喷水视频| 91伊人久久大香线蕉| 亚洲精品第二页| 丁香激情综合国产| 成年女人免费视频| 国产成人午夜片在线观看高清观看| gai在线观看免费高清| 蜜臀久久99精品久久久画质超高清| 激情五月开心婷婷| 校园春色综合网| 国产精品亚洲αv天堂无码| 一本久道久久久| 无罩大乳的熟妇正在播放| 精品1区2区3区4区| 成人性免费视频| 一区二区毛片| 69堂免费视频| 米奇777在线欧美播放| 日本不卡在线观看视频| 香蕉精品999视频一区二区| 欧美在线观看成人| 久久都是精品| 亚洲一区二区蜜桃| 蜜桃在线一区二区三区| 国产亚洲视频一区| 国产精品一区久久久久| 337p日本欧洲亚洲大胆张筱雨| 国产剧情一区二区| 精人妻一区二区三区| 成人高清在线视频| 玖玖爱在线观看| 亚洲国产精品ⅴa在线观看| 日韩免费av一区| 亚洲精品视频在线| 日本特黄一级片| 欧美日韩亚洲一区二区三区| 精品人妻一区二区三区免费看| 在线观看视频一区| 一级日韩一级欧美| 欧美电影精品一区二区| 人妻一区二区三区免费| 亚洲剧情一区二区| 日本中文字幕在线看| 欧美美女18p| 日本在线啊啊| 国产日韩欧美电影在线观看| 日韩有吗在线观看| 久久国产日韩欧美| 97视频精品| 国产日韩欧美精品在线观看| 爽好久久久欧美精品| 日韩av自拍偷拍| 91小视频在线免费看| 污污视频网站在线免费观看| 一区二区三区 在线观看视频| 亚洲久久在线观看| 欧美高清视频在线高清观看mv色露露十八| 精品久久久无码中文字幕| 日韩精品有码在线观看| 黄色免费网站在线观看| 97视频在线观看播放| 久久伊人国产| 精品在线视频一区二区| 色一区二区三区四区| 日韩欧美国产综合在线| 老司机精品视频一区二区三区| 在线观看免费视频黄| 中文在线资源观看网站视频免费不卡| 欧美黄色免费看| 欧美综合一区二区三区| 蜜桃av噜噜一区二区三区麻豆| 一本一本久久a久久精品综合小说 一本一本久久a久久精品牛牛影视 | 国产精品日韩久久久| 毛片毛片毛片毛| 91蜜桃婷婷狠狠久久综合9色| 杨钰莹一级淫片aaaaaa播放| 日韩欧美在线视频日韩欧美在线视频| 国产女人18毛片水真多| 亚洲天堂av综合网| 97人澡人人添人人爽欧美| 91理论片午午论夜理片久久| 嫩草影视亚洲| 免费国产黄色网址| 国产**成人网毛片九色| 开心激情五月网| 色婷婷激情综合| 深夜福利视频网站| 美女av一区二区三区 | 国产精品剧情一区二区在线观看| 欧美在线精品免播放器视频| ady日本映画久久精品一区二区| 亚洲一卡二卡三卡四卡无卡网站在线看| 国产精品一国产精品k频道56| 色偷偷中文字幕| 日韩理论在线观看| 曰批又黄又爽免费视频| 亚洲人午夜色婷婷| 亚洲最新无码中文字幕久久| 国新精品乱码一区二区三区18| 欧美日韩国产探花| 亚洲丝袜在线观看| 亚洲精品国久久99热| 国产美女永久免费| 精品国内自产拍在线观看| 久久久久久一区二区三区四区别墅| 鲁丝一区二区三区免费| 亚洲专区一区| 瑟瑟视频在线观看| 欧美三级免费观看| 免费理论片在线观看播放老| 欧美一级淫片播放口| 亚洲第一福利社区| 丰满少妇被猛烈进入高清播放| 91在线看国产| 超碰中文字幕在线| 亚洲欧美精品一区二区| 午夜日韩成人影院| 日日夜夜精品网站| 免费国产亚洲视频| 天天爽天天爽天天爽| 欧美日韩国产精品成人| 快射视频在线观看| 亚洲a∨日韩av高清在线观看| 一区二区影院| 亚洲精品鲁一鲁一区二区三区| 亚洲一区二区三区四区在线免费观看 | 亚洲精品电影网站| 中文字幕21页在线看| 欧洲在线视频一区| 麻豆国产一区二区| 东方av正在进入| 亚洲精品在线观看视频| 午夜欧美激情| 天天人人精品| 国产伦精品一区二区三区免费迷 | 懂色av一区二区三区在线播放| 黄色精品网站| 爱爱免费小视频| 欧美日韩国产另类一区| 羞羞视频在线免费国产| 国产呦系列欧美呦日韩呦| 亚洲综合丁香| 熟女少妇a性色生活片毛片| 精品蜜桃在线看| 欧美专区福利免费| 日本高清视频免费在线观看| 99免费精品在线观看| 亚洲av无码乱码国产精品fc2| www国产91| 欧美黄色录像| 污污的视频免费| 亚洲成a人v欧美综合天堂下载| 国产黄色免费在线观看| 91手机在线观看| 麻豆精品网站| 清纯粉嫩极品夜夜嗨av| 亚洲日韩欧美视频一区| 免费观看性欧美大片无片| 欧美污视频网站| 一区二区在线免费观看| 免费毛片在线| 99三级在线| 免费成人在线影院| 日本熟妇毛耸耸xxxxxx| 日韩在线视频网站| 欧美调教在线| 中文字幕欧美视频| 欧美亚一区二区| 欧美久久天堂|