Spring循環(huán)依賴實例詳解
是什么
Spring循環(huán)依賴指兩個或多個 Bean 相互依賴,形成閉環(huán)。
Spring 通過巧妙的機制(如三級緩存)默認支持 setter 注入的循環(huán)依賴,但不支持 constructor 注入的循環(huán)依賴(會拋異常)。
兩種注入方式
A、構(gòu)造器注入(如 @Autowired 在 constructor 上):容易造成無法解決的循環(huán)依賴,不推薦使用【現(xiàn)象:無法new對象,因為互相是構(gòu)造函數(shù)的入?yún)?,編譯報錯】,會拋 BeanCurrentlyInCreationException。
/**
* 通過構(gòu)造器的方式注入依賴,構(gòu)造器的方式注入依賴的bean,下面兩個bean循環(huán)依賴
* 測試后發(fā)現(xiàn),構(gòu)造器循環(huán)依賴是無法解決的
*/
public class ClientConstructor {
public static void main(String[] args) {
new ServiceA(new ServiceB(new ServiceA(new ServiceB())));
}
}
B、Setter 注入:通過 setter 方法或字段注入(如 @Autowired 在字段上),Spring 默認支持
@Component
public class A {
@Autowired
private B b; // setter 注入
}
@Component
public class B {
@Autowired
private A a; // setter 注入
}循環(huán)依賴的解決辦法【重點】
重要結(jié)論:Spring 內(nèi)部通過3級緩存來解決循環(huán)依賴,核心是“提前暴露”半成品 Bean(AOP 代理前),允許其他 Bean 先引用。所謂的三級緩存其實就是 Spring 容器內(nèi)部用來解決循環(huán)依賴問題的三個 Map,這三個 Map 在 DefaultSingletonBeanRegistry 類中。

三級緩存詳解:
- singletonObjects(一級緩存):我愿稱之為單例池,常說的 Spring 容器就是指它(因為默認的bean生命周期是singleton),我們獲取單例 bean 就是在這里面獲取的,存放已經(jīng)經(jīng)歷了完整生命周期的Bean對象【存放完全初始化的 Bean】
- earlySingletonObjects(二級緩存):存放早期暴露出來的Bean對象,Bean的生命周期未結(jié)束【存放已經(jīng)實例化,但屬性還未填充完整的半成品 bean】
- singletonFactories(三級緩存):存放可以生成Bean的工廠,用于生產(chǎn)(創(chuàng)建)對象【存放FactoryBean工廠池】
創(chuàng)建過程(以 A 依賴 B,B 依賴 A 為例):
- 實例化 A:Spring 調(diào)用 getBean(A),創(chuàng)建 A 實例(無屬性),放入三級緩存(singletonFactories,作為 lambda:() -> getEarlyBeanReference(A))。
- 注入 A 的屬性:發(fā)現(xiàn)依賴 B,調(diào)用 getBean(B)。
- 實例化 B:類似,創(chuàng)建 B 實例,放入三級緩存。
- 注入 B 的屬性:發(fā)現(xiàn)依賴 A,從三級緩存獲取 A 的工廠,生成早期 A(半成品),放入二級緩存,并注入到 B。
- 完成 B:B 初始化完畢,移到一級緩存。
- 返回到 A:A 繼續(xù)注入 B(已完成),完成初始化,移到一級緩存。
- 如果涉及 AOP:三級緩存的工廠會生成代理對象,確保循環(huán)中代理一致。

getSingleton(): 從容器里面獲得單例的bean,沒有的話則會創(chuàng)建 bean
doCreateBean(): 執(zhí)行創(chuàng)建 bean 的操作(在 Spring 中以 do 開頭的方法都是干實事的方法)
populateBean(): 創(chuàng)建完 bean 之后,對 bean 的屬性進行填充
addSingleton(): bean 初始化完成之后,添加到單例容器池中,下次執(zhí)行 getSingleton() 方法時就能獲取到
Bean的創(chuàng)建順序
Bean的創(chuàng)建順序是由BeanDefinition的注冊順序來決定的, 當然依賴關(guān)系也會影響B(tài)ean創(chuàng)建順序,比如A依賴B,那肯定B先創(chuàng)建,A后創(chuàng)建。
那BeanDefinition的注冊順序由什么來決定的?
主要是由注解(配置)的解析順序來決定,順序如下:
@Configuration @Component @Import-類 @Bean @Import—ImportBeanDefinitionRegistrar
總結(jié)
- 調(diào)用doGetBean()方法,想要獲取beanA,于是調(diào)用getSingleton()方法從緩存中查找beanA
- 在getSingleton()方法中,從一級緩存中查找,沒有,返回null
- doGetBean()方法中獲取到的beanA為null,于是走對應的處理邏輯,調(diào)用getSingleton()的重載方法(參數(shù)為ObjectFactory的)
- 在getSingleton()方法中,先將beanA_name添加到一個集合中,用于標記該bean正在創(chuàng)建中。然后回調(diào)匿名內(nèi)部類的creatBean()方法
- 進入AbstractAutowireCapableBeanFactory#doCreateBean(),先反射調(diào)用構(gòu)造器創(chuàng)建出beanA的實例,然后判斷。是否為單例、是否允許提前暴露引用(對于單例一般為true)、是否正在創(chuàng)建中〈即是否在第四步的集合中)。判斷為true則將beanA添加到【三級緩存】中
- 對beanA進行屬性填充,此時檢測到beanA依賴于beanB,于是開始查找beanB
- 調(diào)用doGetBean()方法,和上面beanA的過程一樣,到緩存中查找beanB,沒有則創(chuàng)建,然后給beanB填充屬性
- 此時beanB依賴于beanA,調(diào)用getsingleton()獲取beanA,依次從一級、二級、三級緩存中找,此時從三級緩存中獲取到beanA的創(chuàng)建工廠,通過創(chuàng)建工廠獲取到singletonObject,此時這個singletonObject指向的就是上面在doCreateBean()方法中實例化的bean
- 這樣beanB就獲取到了beanA的依賴,于是beanB順利完成實例化,并將beanA從三級緩存移動到二級緩存中
- 隨后beanA繼續(xù)他的屬性填充工作,此時也獲取到了beanB,beanA也隨之完成了創(chuàng)建,回到getsingleton()方法中繼續(xù)向下執(zhí)行,將beanA從二級緩存移動到一級緩存中

到此這篇關(guān)于Spring循環(huán)依賴詳解的文章就介紹到這了,更多相關(guān)spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入解析System.load 與 System.loadLibrary
以下是對System.load與System.loadLibrary進行了詳細的分析介紹。需要的朋友可以過來參考下2013-08-08
SpringCloud添加客戶端Eureka Client過程解析
這篇文章主要介紹了SpringCloud添加客戶端Eureka Client過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03
JSP request.setAttribute()詳解及實例
這篇文章主要介紹了 javascript request.setAttribute()詳解及實例的相關(guān)資料,需要的朋友可以參考下2017-02-02
Spring Security基于過濾器實現(xiàn)圖形驗證碼功能
驗證碼就是為了防止惡意用戶采用暴力重試的攻擊手段而設(shè)置的一種防護措施,接下來在Spring Security的環(huán)境中,我們可以用兩種方案實現(xiàn)圖形驗證碼,具體實現(xiàn)方法跟隨小編一起看看吧2021-09-09
Java Swing JComboBox下拉列表框的示例代碼
這篇文章主要介紹了Java Swing JComboBox下拉列表框的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12

