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

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

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

介紹

上圖就是循環(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)題

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

    spring 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-07
  • Spring之AOP兩種代理機(jī)制對(duì)比分析(JDK和CGLib動(dòng)態(tài)代理)

    Spring之AOP兩種代理機(jī)制對(duì)比分析(JDK和CGLib動(dòng)態(tài)代理)

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

    SpringBoot如何配置Controller實(shí)現(xiàn)Web請(qǐng)求處理

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

    java線程封閉之棧封閉和ThreadLocal

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

    Spring Boot入門(web+freemarker)

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

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

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

    Spring Data中domain模塊的使用

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

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

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

    springboot中如何將logback切換為log4j2

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

最新評(píng)論