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