Spring循環(huán)依賴代碼演示及解決方案
介紹
上圖就是循環(huán)依賴的三種情況,雖然方式不同,但是循環(huán)依賴的本質(zhì)是一樣的,就A的完整創(chuàng)建要依賴與B,B的完整創(chuàng)建要依賴于A,相互依賴導(dǎo)致沒辦法完整創(chuàng)建造成失敗.
循環(huán)依賴代碼演示
public class Demo { public static void main(String[] args) { new Demo1(); } } class Demo1{ private Demo2 demo2 = new Demo2(); } class Demo2 { private Demo1 demo1 = new Demo1(); }
上述代碼就是最基本的循環(huán)依賴的場景,Demo1依賴Demo2,Demo2依賴Demo1,然后就報錯了,而上面的這種設(shè)計情況是無解的.
分析問題
首先我們要明確一點就是如果這個對象A還沒創(chuàng)建成功,在創(chuàng)建的過程中要依賴另一個對象B,而另一個對象B也是在創(chuàng)建中要依賴對象A,這種肯定是無解的,這時我們就要緩緩思路,我們先把A創(chuàng)建出來,但是還沒有完成初始化操作,也就是這是一個半成品對象,然后再賦值的時候提前把A暴露出來,然后創(chuàng)建B,讓B創(chuàng)建完成后找到暴露出來的A完成整體的實例化,這時再把B交給A完成A的后續(xù)操作.從而解決循環(huán)依賴,也就是下圖:
代碼解決
public class Demo { /** * 保存提前暴露的對象,也就是半成品對象 */ private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); public static void main(String[] args) throws Exception { System.out.println(getBean(Demo1.class).getDemo2()); System.out.println(getBean(Demo2.class).getDemo1()); } private static <T> T getBean(Class<T> clazz) throws Exception { // 獲取beanName String beanName = clazz.getName().toLowerCase(); // 查找緩存中是否存在半成品對象 if (singletonObjects.containsKey(beanName)) { return (T) singletonObjects.get(beanName); } // 緩存中不存在半成品對象,反射進行實例化 T res = clazz.newInstance(); // 將實例化后的對象儲存到緩存 singletonObjects.put(beanName, res); // 獲取所有屬性 Field[] fields = res.getClass().getDeclaredFields(); // 循環(huán)進行屬性填充 for (Field field : fields) { // 針對private修飾 field.setAccessible(Boolean.TRUE); // 獲取屬性類型 Class<?> fieldClazz = field.getType(); // 獲取屬性beanName String filedBeanName = fieldClazz.getName().toLowerCase(); // 屬性填充,查找緩存是否有對應(yīng)屬性,沒有就遞歸調(diào)用 field.set(res, singletonObjects.containsKey(filedBeanName) ? singletonObjects.get(filedBeanName) : getBean(fieldClazz)); } return res; } } class Demo1 { private Demo2 demo2; public Demo2 getDemo2() { return demo2; } public void setDemo2(Demo2 demo2) { this.demo2 = demo2; } } class Demo2 { private Demo1 demo1; public Demo1 getDemo1() { return demo1; } public void setDemo1(Demo1 demo1) { this.demo1 = demo1; } }
在上面的方法中核心就是getBean方法,Demo1創(chuàng)建后填充屬性時依賴Demo2,那么就去創(chuàng)建Demo2,在創(chuàng)建Demo2開始填充時發(fā)現(xiàn)依賴Demo1,但此時Demo1這個半成品對象已經(jīng)放在緩存singletonObjects中了,所以Demo2正常創(chuàng)建,再結(jié)束遞歸把Demo1也創(chuàng)建完整了.
Spring循環(huán)依賴
針對Spring中Bean對象的各種場景,支持的方案不一樣
單例
- 構(gòu)造注入:無解,避免棧溢出,需要檢測是否存在循環(huán)依賴的情況,如果有直接拋異常
- 設(shè)值注入:三級緩存–>提前暴露
原型
- 構(gòu)造注入:無解,避免棧溢出,需要檢測是否存在循環(huán)依賴的情況,如果有直接拋異常
- 設(shè)置注入:不支持循環(huán)依賴
Spring是如何解決循環(huán)依賴問題的?上述代碼中對象的生命周期就兩個:創(chuàng)建對象和屬性填充,而Spring涉及到對象生命周期的方法就很多了,簡單舉例,如下圖:
基于對上述代碼的了解,我們知道肯定需要在調(diào)用構(gòu)造方法創(chuàng)建完成后再暴露對象,再Spring中提供了三級緩存來處理這個事情,如下圖:
對應(yīng)到源碼中具體處理循環(huán)依賴的流程如下:
上面就是Spring的生命周期方法和循環(huán)依賴出現(xiàn)相關(guān)的流程了.下面就是放入三級緩存的源碼:
/** * 添加對象到三級緩存 * * @param beanName * @param singletonFactory */ protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { // 確保singletonFactory不為null Assert.notNull(singletonFactory, "Singleton factory must not be null"); // 使用singletonObjects進行加鎖,保證線程安全 synchronized (this.singletonObjects) { //如果singletonObjects緩存中沒有該對象 if (!this.singletonObjects.containsKey(beanName)) { // 將對象放置到singletonFactories(三級緩存)中 this.singletonFactories.put(beanName, singletonFactory); // 從earlySingletonObjects(二級緩存)中移除該對象 this.earlySingletonObjects.remove(beanName); // 將beanName添加到已經(jīng)注冊的單例集中 this.registeredSingletons.add(beanName); } } }
放入二級緩存的源碼:
/** * 返回在給定名稱下注冊的(原始)單例對象.檢查已經(jīng)實例化的單例,并允許對當(dāng)前創(chuàng)建的單例進行早期引用(解決循環(huán)引用) * * @param beanName * @param allowEarlyReference * @return */ protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 不需要完全獲取單例鎖的情況下快速檢查現(xiàn)有實例 Object singletonObject = this.singletonObjects.get(beanName); // 如果單例對象為空,并且當(dāng)前單例正在創(chuàng)建中,則嘗試獲取早期單例對象 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); // 如果早期單例對象為空,并且允許早期引用,則再完全獲取單力所的情況下創(chuàng)建早期單例對象 if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // 檢查早期單例對象是否存在 singletonObject = this.singletonObjects.get(beanName); // 如果早期對象仍然為空則創(chuàng)建單例對象 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; }
放入一級緩存中的源碼:
/** * 將單例對象添加到一級緩存 * * @param beanName * @param singletonObject */ protected void addSingleton(String beanName, Object singletonObject) { // 使用singletonObjects進行加鎖,保證線程安全 synchronized (this.singletonObjects) { // 將映射關(guān)系添加到一級緩存 this.singletonObjects.put(beanName, singletonObject); // 從三級緩存;二級緩存中移除該對象 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); // 將beanName添加到已經(jīng)注冊的單例集中 this.registeredSingletons.add(beanName); } }
總結(jié)
三級緩存分別有什么作用
- singletonObjects:緩存經(jīng)過了完整生命周期的bean
- earlySingletonObjects:緩存未經(jīng)過完整生命周期的bean,如果某個bean出現(xiàn)了循環(huán)依賴,就會提前把這個暫時未經(jīng)過完整生命周期的bean放入earlySingletonObjects中,如果這個bean要經(jīng)過AOP,那么就會把代理對象放入到earlySingletonObjects中,否則就是把原始對象放入earlySingletonObjects,但是不管怎么樣就是代理對象,代理對象所代理的原始對象也是沒有經(jīng)過完整生命周期的,所以放入earlySingletonObjects我們就可以統(tǒng)一認為是未經(jīng)過完整生命周期的bean
- singletonFactories:緩存的是一個ObjectFactory,也就是一個Lambda表達式,在每個bean的生成過程中,經(jīng)過實例化得到一個原始對象后,都會提前基于原始對象暴露一個Lambda表達式,并保存到三級緩存中,這個Lambda表達式可能用到,也可能用不到, 如果當(dāng)前bean沒有出現(xiàn)循環(huán)依賴,那么這個Lambda表達式就沒有用,當(dāng)前bean按照自己的生命周期正常執(zhí)行,執(zhí)行完直接把當(dāng)前bean放入singletonObjects中,如果當(dāng)前bean在依賴注入時出現(xiàn)了循環(huán)依賴,則從三級緩存中拿到Lambda表達式,并執(zhí)行Lambda表達式得到一個對象,并把得到的對象放入到二級緩存(如果當(dāng)前bean需要AOP,那么執(zhí)行Lambda表達式,得到的就是對應(yīng)的代理對象,如果無需AOP,則直接得到一個原始對象)
- 其實還要一個緩存,用來記錄某個原始對象是否進行過AOP了
為什么需要三級緩存
如果A的原始對象注入給B的屬性之后,A的原始對象進行了AOP產(chǎn)生了一個代理對象,此時就會出現(xiàn),對于A而言,它的bean對象應(yīng)該是AOP之后的代理對象,而B的a屬性對應(yīng)的不是AOP之后的代理對象,這就產(chǎn)生了沖突,B依賴的A和最終的A不是同一個對象,三級緩存主要處理的是AOP的代理對象,存儲的是一個ObjectFactory
到此這篇關(guān)于Spring循環(huán)依賴代碼演示及解決方案的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
聊聊Spring?Cloud?Gateway過濾器精確控制異常返回問題
這篇文章主要介紹了Spring?Cloud?Gateway過濾器精確控制異常返回問題,本篇任務(wù)就是分析上述現(xiàn)象的原因,通過閱讀源碼搞清楚返回碼和響應(yīng)body生成的具體邏輯,需要的朋友可以參考下2021-11-11spring cloud gateway全局過濾器實現(xiàn)向request header中放數(shù)據(jù)
這篇文章主要介紹了spring cloud gateway全局過濾器實現(xiàn)向request header中放數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理)
這篇文章主要介紹了Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05SpringBoot如何配置Controller實現(xiàn)Web請求處理
這篇文章主要介紹了SpringBoot如何配置Controller實現(xiàn)Web請求處理,文中通過圖解示例介紹的很詳細,具有有一定的參考價值,需要的小伙伴可以參考一下2023-05-05Jenkins遷移job插件Job Import Plugin流程詳解
這篇文章主要介紹了Jenkins遷移job插件Job Import Plugin流程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08springboot中如何將logback切換為log4j2
springboot默認使用logback作為日志記錄框架,常見的日志記錄框架有l(wèi)og4j、logback、log4j2,這篇文章我們來學(xué)習(xí)怎樣將logbak替換為log4j2,需要的朋友可以參考下2023-06-06