面試題:Spring循環依賴問題


Spring是怎么解決循環依賴的?

首先,Spring 解決循環依賴有兩個前提條件:

  1. 不全是構造器方式的循環依賴
  2. 必須是單例

基於上面的問題,我們知道Bean的生命周期,本質上解決循環依賴的問題就是三級緩存,通過三級緩存提前拿到未初始化的對象。

第一級緩存:用來保存實例化、初始化都完成的對象

第二級緩存:用來保存實例化完成,但是未初始化完成的對象

第三級緩存:用來保存一個對象工廠,提供一個匿名內部類,用於創建二級緩存中的對象

image-20210111234717861

假設一個簡單的循環依賴場景,A、B互相依賴。

image-20210111234733721

A對象的創建過程:

  1. 創建對象A,實例化的時候把A對象工廠放入三級緩存

image-20210111234804804

  1. A注入屬性時,發現依賴B,轉而去實例化B

  2. 同樣創建對象B,注入屬性時發現依賴A,一次從一級到三級緩存查詢A,從三級緩存通過對象工廠拿到A,把A放入二級緩存,同時刪除三級緩存中的A,此時,B已經實例化並且初始化完成,把B放入一級緩存。

image-20210111234852793

  1. 接着繼續創建A,順利從一級緩存拿到實例化且初始化完成的B對象,A對象創建也完成,刪除二級緩存中的A,同時把A放入一級緩存

  2. 最后,一級緩存中保存着實例化、初始化都完成的A、B對象

image-20210111234918883

因此,由於把實例化和初始化的流程分開了,所以如果都是用構造器的話,就沒法分離這個操作,所以都是構造器的話就無法解決循環依賴的問題了。

具體的方法:

  • 調用doGetBean()方法,想要獲取beanA,於是調用getSingleton()方法從緩存中查詢beanA
  • getSingleton()方法屮,從一級緩存中查找,沒有,返回null
  • doGetBean()方法屮獲取到的beanA為null, 於是走對應的處理邏輯,調用getSingleton()的重載方法(參數為ObjectFactory的)
  • 在getSingleton()方法中,先將beanA_name添加到一個集合中,用於標記bean創建中。然后回調匿名內部類的creatBean方法
  • 進入AbstractAutowireCapableBeanFactory#doCreateBean,先反射調用構選器創建出beanA的實例。然后判斷:是否為單例、足否允許提前暴露引用(對於單例一般為true)、是否正在創建中(即是否在第四步的集合中)。判斷力true則將beanA添加到【三級緩存】中。
  • 對beanA進行屬性填充,此時檢測到 beanA依賴beanB,於是開始查找beanB
  • 調用doGetBean()方法,和上面beanA的過程一樣,到緩存中查找beanB,沒行則創建,然后給beanB填充屬性。
  • 此時beanB依賴於beanA,調用getSingleton()獲取beanA,依次從一級、二級、三級緩存中找,此時從三級緩存中獲取到beanA的創建工廠,通過創建工廠獲取到singletonObject,此這個 singletonObject指向的就是在上面在在doCreateBean()方法中實例化的beanA 。
  • 這樣beanB就獲取到beanA的依賴,於是beanB順利完成實例化,並將beanA從三級緩存移動到二級緩存中。
  • 隨后 beanA繼續他的屬性填充工作,此時也獲取到beanB, beanA也隨之完成創建,回到getSingleton()方法屮繼續向下執行,將beanA從二級緩存移動到一級緩存中。

image-20210112001717811

為什么要三級緩存?二級不行嗎?

不可以,主要是為了生成代理對象。

因為三級緩存中放的是生成具體對象的匿名內部類,value為一個lambda表達式,他可以生成代理對象,也可以是普通的實例對象。

使用三級緩存主要是為了保證不管什么時候使用的都是一個對象。

假設只有二級緩存的情況,往二級緩存中放的顯示一個普通的Bean對象,BeanPostProcessor去生成代理對象之后,覆蓋掉二級緩存中的普通Bean對象,那么多線程環境下可能取到的對象就不一致了。

image-20210111235041954


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM