欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring循環(huán)依賴代碼演示及解決方案

 更新時間:2023年04月21日 11:38:54   作者:.番茄炒蛋  
這篇文章主要介紹了Spring循環(huán)依賴實現(xiàn)過程,Spring的解決循環(huán)依賴是有前置條件的,要解決循環(huán)依賴我們首先要了解Spring Bean對象的創(chuàng)建過程和依賴注入的方式

介紹

上圖就是循環(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過濾器精確控制異常返回問題

    這篇文章主要介紹了Spring?Cloud?Gateway過濾器精確控制異常返回問題,本篇任務(wù)就是分析上述現(xiàn)象的原因,通過閱讀源碼搞清楚返回碼和響應(yīng)body生成的具體邏輯,需要的朋友可以參考下
    2021-11-11
  • spring cloud gateway全局過濾器實現(xiàn)向request header中放數(shù)據(jù)

    spring cloud gateway全局過濾器實現(xiàn)向request header中放數(shù)據(jù)

    這篇文章主要介紹了spring cloud gateway全局過濾器實現(xiàn)向request header中放數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理)

    Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理)

    這篇文章主要介紹了Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • SpringBoot如何配置Controller實現(xiàn)Web請求處理

    SpringBoot如何配置Controller實現(xiàn)Web請求處理

    這篇文章主要介紹了SpringBoot如何配置Controller實現(xiàn)Web請求處理,文中通過圖解示例介紹的很詳細,具有有一定的參考價值,需要的小伙伴可以參考一下
    2023-05-05
  • java線程封閉之棧封閉和ThreadLocal

    java線程封閉之棧封閉和ThreadLocal

    這篇文章主要介紹了java線程封閉之棧封閉和ThreadLocal,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • Spring Boot入門(web+freemarker)

    Spring Boot入門(web+freemarker)

    這篇文章主要介紹了Spring Boot入門(web+freemarker)的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Java深入理解代碼塊的使用細節(jié)

    Java深入理解代碼塊的使用細節(jié)

    所謂代碼塊是指用"{}"括起來的一段代碼,根據(jù)其位置和聲明的不同,可以分為普通代碼塊、構(gòu)造塊、靜態(tài)塊、和同步代碼塊。如果在代碼塊前加上?synchronized關(guān)鍵字,則此代碼塊就成為同步代碼塊
    2022-05-05
  • Spring Data中domain模塊的使用

    Spring Data中domain模塊的使用

    Spring Data是一個流行的數(shù)據(jù)訪問框架,本文主要介紹了Spring Data中domain模塊的使用,并展示如何使用它來優(yōu)化我們的數(shù)據(jù)訪問層,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Jenkins遷移job插件Job Import Plugin流程詳解

    Jenkins遷移job插件Job Import Plugin流程詳解

    這篇文章主要介紹了Jenkins遷移job插件Job Import Plugin流程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-08-08
  • springboot中如何將logback切換為log4j2

    springboot中如何將logback切換為log4j2

    springboot默認使用logback作為日志記錄框架,常見的日志記錄框架有l(wèi)og4j、logback、log4j2,這篇文章我們來學(xué)習(xí)怎樣將logbak替換為log4j2,需要的朋友可以參考下
    2023-06-06

最新評論