Java?Spring?循環(huán)依賴解析
1、常見問題
- 你解釋一下 spring 中的三級緩存?
- 三級緩存分別是什么?三個 Map 有什么異同?
- 什么是循環(huán)依賴?請談?wù)??你看過 spring 的源碼嗎?一般我們說的是 spring 容器是什么?
- 多例的情況下,循環(huán)依賴問題為什么無法解決?
2、什么是循環(huán)依賴?
多個 bean 之間相互依賴,形成閉環(huán)。比如:A 依賴于 B, B 依賴于 C , C 依賴于 A
示例代碼:
public class T1 { ? class A { ? ? B b; ? } ? class B { ? ? C c; ? } ? class C { ? ? A a;? ? } }
比如:A 依賴于 B, B 依賴于 C , C 依賴于 A
通常來說,如果問 Spring 容器內(nèi)部如何解決循環(huán)依賴,一定是指默認的單例 Bean 中, 屬性相互引用的場景。
@Component public class A { ? ? @Autowired ?? ?private B b; } @Component public class B { ? ? @Autowired ?? ?private C c; } ? @Component public class C { ? ? @Autowired ?? ?private A a; }
3、循環(huán)依賴說明
官方說明:
構(gòu)造方法注入:不支持循環(huán)依賴。
我們 AB 循環(huán)依賴問題。只要 A 的方式是 setter 且 singleton
就不會有循環(huán)依賴問題。
4、BeanCurrentlyInCreationException
循環(huán)依賴異常的定義如下所示,如果出現(xiàn)循環(huán)依賴,我們在啟動/運行過程中會報這個錯誤。
5、依賴注入的兩種方式
方式一:構(gòu)造器方式注入依賴
@Component public class ServiceA{ ? ? private ServiceB serviceB; ? ? ? ?? ? ? public ServiceA(ServiceB serverB) { ? ? ? ?this.serivceB = serviceB; ? ? } ? ?? } @Component public class ServiceB{ ? ? private ServiceA serviceA; ? ? ? ?? ? ? public ServiceB(ServiceA serviceA) { ? ? ? ?this.serviceA = serviceA; ? ? } ? ?? }
構(gòu)造器循環(huán)依賴是無法解決的,你想讓構(gòu)造器注入支持循環(huán)依賴,是不可能的。
方式二:以 set 方式注入依賴
@Component public class ServiceA{ ? ? private ServiceB serviceB; ? ? ? ?? ? ? public setServiceB(ServiceB serverB) { ? ? ? ?this.serivceB = serviceB; ? ? } ? ?? } @Component public class ServiceB{ ? ? private ServiceA serviceA; ? ? ? ?? ? ? public setServiceB(ServiceA serviceA) { ? ? ? ?this.serviceA = serviceA; ? ? } ? ?? }
案例演示(基于 Spring 容器的循環(huán)依賴)
普通的 Java 基礎(chǔ)編碼A 類、B 類
@Data public class A{ ? private B b; ?? ? public A() { ? ? ?System.out.println("----- A create success"); ? } } @Data public class B{ ? private a a; ? public B() { ? ? ?System.out.println("------ B create success"); ? } ? ? }
循環(huán)依賴解決
A a = new A(); B b = new B(); a.setB(b); b.setA(a);
基于 Spring 容器的循環(huán)依賴
- 默認的單例(singleton)的場景是支持循環(huán)依賴的,不報錯
- 原型(prototype)的場景是不支持循環(huán)依賴的, 會報錯
代碼演示:
循環(huán)依賴代碼:
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class A { ? ? @Autowired ? ? private B b; } @Component public class B { ? ? @Autowired ? ? private A a; }
默認單例,修改為原型
// 增加注解 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
一段測試程序
class BTest { ? ? @Configuration ? ? @Import({A.class, B.class}) ? ? public static class TestConfig { ? ? } ? ? @Test ? ? public void currentlyincreation() { ? ? ? ? AnnotationConfigApplicationContext applicationContext = ? ? ? ? ? ? ? ? new AnnotationConfigApplicationContext(TestConfig.class); ? ? ? ? // 這里要獲取 bean 一下,如果不去主動獲取,可能是由于惰性加載沒有執(zhí)行,不會報錯 ? ? ? ? A a = applicationContext.getBean(A.class); ? ? ? ? System.out.println(a); ? ? } }
循環(huán)依賴異常
6、Spring 三級緩存介紹和循環(huán)依賴解決過程
三級緩存介紹
核心類: DefaultSingletonBeanRegistry
第一級緩存:(也叫單例池)singletonObjects: 存放已經(jīng)經(jīng)歷完整生命周期的 Bean 對象
第二級緩存:earlySingletonObjects: 存放早起暴露出來的 Bean 對象, Bean 的生命周期未結(jié)束(屬性還未填充完成)
第三級緩存:Map<String, ObjectFactory<?>> singletonFactories可以存放 Bean 工廠。
只有單例 Bean 會通過三級緩存提前暴露出來解決循環(huán)依賴問題,而非單例的 bean, 每次從容器獲取都是新的對象,都會重新創(chuàng)建,所以非單例的 bean 是沒有緩存的,不會放到三級緩存中。
實例化/初始化定義
實例化/初始化
1、實例化:內(nèi)存中申請一塊內(nèi)存空間;(比如:租賃好好房子,自己的家具沙發(fā),床還沒有搬進去。)
2、初始化屬性填充:完成各種屬性的賦值;(比如:裝修,家電家具進場)
三級緩存使用過程
3 個 map 的四大方法,總體相關(guān)對象
第一層:(也叫單例池)singletonObjects: 存放已經(jīng)初始化好的 Bean。
第二層:earlySingletonObjects: 存放的是實例化的了, 但是未初始化的 Bean。
第三層:Map<String, ObjectFactory<?>> singletonFactories
存放的是 FactroyBean。假如 A 類實現(xiàn)了 FactoryBean, 那么依賴注入的時候不是 A 類,而是 A類的 FAC天FactoryBean。
/** ?* 單例對象的緩存:bean 名稱--Bean 實例, 即:所有的單例池 ?* 表示經(jīng)歷了完整生命周期的 Bean 對象 ?* <b>第一級緩存</b> ?*/ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** ?* 單例工廠的高速緩存:bean 名稱--ObjectFacotry ?* 表示存放生成的 bean 工廠 ?* <b>第三級緩存</b> ?*/ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** ?* 早起的單例對象的高速緩存:bean 名稱--Bean 實例 ?* 表示 Bean 的生命周期還沒有走完(Bean 的屬性還未填充)就把這個 Bean 存入該緩存中 ?* 也就是實例化的 bean 放入了該緩存中 ?* <b>第二級緩存</b> ?*/ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
A/B 兩對象在三級緩沖的遷移說明
1、創(chuàng)建 A 過程中需要 B, 于是 A 將自己放入到三級緩存里面,去實例化 B
2、B 實例化的時候發(fā)現(xiàn)需要 A ,于是 B 先查一級緩存,沒有,再查二級緩存,還是沒有再查三級緩存,找到了 A 然后把三級緩存里面的這個 A 放入到二級緩存里面,并且刪除三級緩存里面的 A。
3、 B 順利初始化完畢后,將自己放入到一級緩存里面(此時 B 里面的 A 依然是創(chuàng)建中狀態(tài))后來接著創(chuàng)建 A, 此時 B 已經(jīng)創(chuàng)建結(jié)束,直接從一級緩存里面拿到 B,然后完成創(chuàng)建,并且將 A 自己放到一級緩存中
ObjectFactory 接口
@FunctionalInterface public interface ObjectFactory<T> { ? ? /** ?? ? * Return an instance (possibly shared or independent) ?? ? * of the object managed by this factory. ?? ? * @return the resulting instance ?? ? * @throws BeansException in case of creation errors ?? ? */ ? ? T getObject() throws BeansException; }
DEBUG 斷點調(diào)試
1、Bean 創(chuàng)建
2、放入三級緩存
3、屬性填充(就是做屬性注入)
循環(huán)依賴解決
整理流程梳理:
1、調(diào)用 doGetBean() 方法 , 想要獲取 beanA , 于是調(diào)用 getSingletion() 方法從緩存中查詢 beanA
2、在 getSingletion() 方法中,從一級緩存中查找,沒有,返回 null
3、doGetBean() 犯法中獲取到 beanA 為 null, 于是就走對應(yīng)的處理邏輯,調(diào)用 getSIngletion() 的重載方法(參數(shù)為 ObjecFactory 的)
4、在 getSingletion() 方法中,先將 beanB_name 添加到一個集合中,用于標(biāo)記該 bean 正在創(chuàng)建中,然后回調(diào)匿名內(nèi)部類的 createBean 方法
5、進入 AbstractAutowireCapableBenaFactory#doCreateBean , 先反射調(diào)用構(gòu)造器創(chuàng)建出 beanA 的實例,然后判斷,是否為單例、是否允許提前暴露引用(對于單例一般為 true)、是否正在創(chuàng)建中(即是在第四步的集合中)。判斷為 true 則將 beanA 添加到【三級緩存】中。
6、對 beanA 進行屬性填充,此時檢測到 beanA 依賴 beanB , 于是開始查找 beanB
7、調(diào)用 doGetBean 方法,和上面的 beanA 過程一樣,到緩存中查找 beanB, 沒有則創(chuàng)建,然后給 beanB 填充屬性。
8、此時 beanB 依賴于 beanA , 調(diào)用 getSingleton() 獲取 beanA , 依次從一級、二級、三級緩存中找,此時三級緩存中獲取到 beanA 的創(chuàng)建工廠,通過創(chuàng)建工廠獲取到 singletonObject, 此時這個 singletonObject 指向的就是上面的 doCreateBean() 方法實例化的 beanA 。
9、這樣 beanB 就獲取到了 beanA 的依賴,于是 beanB 順利完成實例話,并將 beanA 從三級緩存移動到二級緩存中。
10、隨后 beanA 繼續(xù)他的屬性填充工作,此時也獲取到了beanB , beanA 也隨之完成了創(chuàng)建,回調(diào) getSingleton() 方法中繼續(xù)向下執(zhí)行,將 beanA 從二級緩存中移動到一級緩存中。
7、Spring 循環(huán)依賴總結(jié)
Spring 創(chuàng)建 Bean 主要分為兩個步驟,創(chuàng)建原始 Bean 對象,接著去填充屬性和初始化。
每次創(chuàng)建 Bean 之前,我們都會從緩存中查一下有沒有該 bean , 因為是單例, 只能有一個
當(dāng)我們創(chuàng)建 beanA 的原始對象之后,并且把它放入到三級緩存中,接下來就該填充屬性了,這個時候發(fā)現(xiàn)依賴了 beanB , 就直接去創(chuàng)建 beanB, 同樣的流程,創(chuàng)建完 beanB 統(tǒng)籌國內(nèi)屬性時又發(fā)現(xiàn)了它依賴了 beanA 又是同樣的流程。
不同的是:
這個時候可以在三級緩存只能夠查詢到剛才放進去的原始對象 beanA , 所以不需要繼續(xù)創(chuàng)建,用它注入 beanB , 完成 beanB 的創(chuàng)建
既然 beanB 創(chuàng)建好了,所以 beanA 就可以彎沉屬性填充的步驟了,接下來執(zhí)行剩下的邏輯完,完成閉環(huán)
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { ?? ?// 從 singletonObject 獲取實例,singletonObject 中的實例都是準(zhǔn)備好的 bean 實例 ? ? Object singletonObject = this.singletonObjects.get(beanName); ? ? // isSingletonCurrentlyInCreation() 判斷當(dāng)前單例 bean 是否正在創(chuàng)建中 ? ? if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { ? ? ? ?? ? ? ? ? singletonObject = this.earlySingletonObjects.get(beanName); ? ? ? ? if (singletonObject == null && allowEarlyReference) { ? ? ? ? ? ? synchronized (this.singletonObjects) { ? ? ? ? ? ? ? ? // Consistent creation of early reference within full singleton lock ? ? ? ? ? ? ? ? singletonObject = this.singletonObjects.get(beanName); ? ? ? ? ? ? ? ? if (singletonObject == null) { ?? ? ? ? ? ??? ??? ?// 一級緩存中沒有就去二級緩存中找?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? singletonObject = this.earlySingletonObjects.get(beanName); ? ? ? ? ? ? ? ? ? ? if (singletonObject == null) { ? ? ? ? ? ? ? ? ? ? ? ? // 二級緩存也沒有,就去三級緩存查找 ? ? ? ? ? ? ? ? ? ? ? ? ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); ? ? ? ? ? ? ? ? ? ? ? ? if (singletonFactory != null) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 三級緩存存在的化,就把它移動到二級緩存 ? ? ? ? ? ? ? ? ? ? ? ? ? ? singletonObject = singletonFactory.getObject(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? this.earlySingletonObjects.put(beanName, singletonObject); ? ? ? ? ? ? ? ? ? ? ? ? ? ? this.singletonFactories.remove(beanName); ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ? return singletonObject; }
Spring 解決循環(huán)依賴靠的是 Bean 的“中間態(tài)” 這個概念。而這個中間態(tài)指的是 已經(jīng)實例化還沒有初始化的狀態(tài) ---> 半成品,實例話的過程有是通過構(gòu)造器創(chuàng)建的,如果 A 還沒有創(chuàng)建好出來怎么可能提前曝光,所以構(gòu)造器的循環(huán)依賴無法解決。
Spring 為了解決單例的循環(huán)依賴問題,使用了三級緩存:
- 其中一級緩存為單例池(singletonObjects)
- 二級緩存為提前曝光對象(earlySingletonObjects)
- 三級緩存為提前曝光對象工廠(singletonFactories)
假設(shè) A,B 循環(huán)引用,實例化 A 的時候就將其放入三級緩存中,接著填充屬性的時候,發(fā)現(xiàn)依賴了 B ,同樣的流程也是實例化后放入三級緩存,接著去填充屬性時發(fā)現(xiàn)自己依賴 A,這個時候從緩存中查找到早起暴露的 A,沒有 AOP 代理的化,直接將 A 原始對象注入 B,完成 B的初始化后,進行屬性填充和初始化,這個時候 B完成后,就去完成剩下 A的步驟,如果有 AOP 代理,就會進行 AOP 處理獲取代理后的 A,注入 B, 走剩下的流程。
到此這篇關(guān)于Java Spring 循環(huán)依賴解析的文章就介紹到這了,更多相關(guān)Spring 循環(huán)依賴解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea一鍵部署SpringBoot項目jar包到服務(wù)器的實現(xiàn)
我們在開發(fā)環(huán)境部署項目一般通過idea將項目打包成jar包,然后連接linux服務(wù)器,將jar手動上傳到服務(wù)中,本文就來詳細的介紹一下步驟,感興趣的可以了解一下2023-12-12springboot-dubbo cannot be cast to問題及解決
這篇文章主要介紹了springboot-dubbo cannot be cast to問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04springcloud集成nacos?使用lb?無效問題解決方案
這篇文章主要介紹了解決springcloud集成nacos?使用lb?無效,通過查看spring-cloud-starter-gateway?jar中的自動配置類的源碼,得知,該jar包中是不支持負載均衡的,需要引入spring-cloud-starter-loadbalancer?來支持,需要的朋友可以參考下2023-04-04Spring動態(tài)配置計時器觸發(fā)時間的實例代碼
這篇文章主要介紹了Spring動態(tài)配置計時器觸發(fā)時間的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06Java?20在Windows11系統(tǒng)下的簡易安裝教程
這篇文章主要給大家介紹了關(guān)于Java?20在Windows11系統(tǒng)下的簡易安裝教程,學(xué)習(xí)Java的同學(xué),第一步就是安裝好Java環(huán)境,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-07-07SpringCloud Alibaba項目實戰(zhàn)之nacos-server服務(wù)搭建過程
Nacos 是阿里巴巴推出來的一個新開源項目,這是一個更易于構(gòu)建云原生應(yīng)用的動態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)管理平臺。本章節(jié)重點給大家介紹SpringCloud Alibaba項目實戰(zhàn)之nacos-server服務(wù)搭建過程,感興趣的朋友一起看看吧2021-06-06