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

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

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

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

