最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文詳解Spring是怎樣處理循環(huán)依賴的

 更新時(shí)間:2024年01月24日 11:06:47   作者:后端開發(fā)萌新  
循環(huán)依賴簡(jiǎn)單理解就是A,B 兩個(gè)bean相互依賴,A依賴B,B依賴A,A->B、B->A大概就是這樣,這篇文章主要介紹了Spring是怎樣處理循環(huán)依賴的,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下

環(huán)境

Spring Framework Version: 5.3.x

Gradle Version:7.5.1

什么是循環(huán)依賴?

簡(jiǎn)單理解就是A,B 兩個(gè)bean相互依賴,A依賴B,B依賴A

A->B、B->A大概就是這樣.

所有注入場(chǎng)景的循環(huán)依賴Spring都能解決嗎?

答案是 “不”, Spring不能夠解決循環(huán)依賴的構(gòu)造器注入,其它的注入方式都能解決

**注意:**能解決非構(gòu)造器注入的循環(huán)依賴的前提是開啟允許循環(huán)依賴(allowCircularReferences = true),在spring中默認(rèn)開啟,如果是在springboot2.x中,那么是默認(rèn)關(guān)閉的

場(chǎng)景

我們來(lái)編寫一段代碼,模擬一下循環(huán)依賴場(chǎng)景

TestA.java

public class TestA {
	private TestB testB;

	public void setB(TestB testB) {
		this.testB = testB;
	}

	public void testCircularReference() {
		System.out.println("TestA bean register success...");
	}
}

TestB.java

public class TestB {
	private TestA testA;

	public void setA(TestA testA) {
		this.testA = testA;
	}

	public void testCircularReference() {
		System.out.println("TestB bean register success...");
	}
}

spring-context.xml

//使用的注入方式是自動(dòng)裝配,根據(jù)type自動(dòng)裝配
<bean id="testA" class="com.spring.demo.circularreference.TestA" autowire="byType"></bean>
<bean id="testB" class="com.spring.demo.circularreference.TestB" autowire="byType"></bean>

Main.java

public class Main {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
		TestA testA = context.getBean(TestA.class);
		TestB testB = context.getBean(TestB.class);
		testA.testCircularReference();
		testB.testCircularReference();
    }
}

測(cè)試結(jié)果

a bean register success...
b bean register success...

好,代碼編寫完畢,測(cè)試也沒(méi)問(wèn)題,接下來(lái)我們來(lái)進(jìn)行源碼解析

Spring是怎么解決循環(huán)依賴的?

前置說(shuō)明

Spring是通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴的,我們來(lái)看一張圖片,認(rèn)識(shí)一下三級(jí)緩存

在這里插入圖片描述

一級(jí)緩存(singletonObjects):存放初始化完成的bean

二級(jí)緩存(earlySingletonObjects):存放實(shí)例化但未初始化的bean

三級(jí)緩存(singletonFactories):存放對(duì)象工廠,也就是把實(shí)例化但未初始化的bean包裝為ObjectFactory

認(rèn)識(shí)了三級(jí)緩存后,我們來(lái)剖析一下Spring是怎么利用三級(jí)緩存來(lái)解決循環(huán)依賴的

在實(shí)例化TestA之后,我們先會(huì)把TestA添加到三級(jí)緩存,然后進(jìn)行屬性填充,給TestB進(jìn)行依賴注入,那么到TestB屬性填充時(shí),發(fā)現(xiàn)TestB也依賴TestA,這個(gè)時(shí)候去注入TestA,先從一級(jí)緩存獲取,獲取不到,再?gòu)亩?jí)緩存獲取,二級(jí)緩存也沒(méi)有,這個(gè)時(shí)候從三級(jí)緩存獲取,獲取到了,然后getObject()生成TestA對(duì)象(在這個(gè)時(shí)候,如果有代理,那么生成代理對(duì)象,如果沒(méi)有代理,直接返回原本的對(duì)象),獲取到TestA對(duì)象后移到二級(jí)緩存并返回,這個(gè)時(shí)候TestB就成功注入了TestA,然后TestB順利的走完生命周期,就回到了第一個(gè)TestA,進(jìn)行初始化,初始化完之后,再?gòu)亩?jí)緩存中取出再重新賦值(為了保證bean是同一個(gè)),最后添加到一級(jí)緩存中

這么說(shuō)可能有點(diǎn)繞,我們結(jié)合一張圖來(lái)看看

在這里插入圖片描述

大概就是這樣

  • 添加半成品的TestA(實(shí)例化但沒(méi)進(jìn)行屬性注入與初始化)到三級(jí)緩存,然后注入TestB,在TestB中也要進(jìn)行屬性注入,然后就去注入TestA
  • 在TestB中注入TestA時(shí),直接從緩存獲取,因?yàn)檫@時(shí)三級(jí)緩存已經(jīng)存在TestA,然后調(diào)用getEarlyBeanReference方法生成對(duì)象(如果有代理也是在此生成)
  • 添加到二級(jí)緩存中并刪除三級(jí)緩存,最后返回,TestB屬性注入完成,繼續(xù)走Bean的生命周期
  • 回到原本TestA,這時(shí)TestB注入已經(jīng)完成,然后初始化,最后從二級(jí)緩存中獲取最新的bean,避免不是同一個(gè)對(duì)象(代理對(duì)象)

源碼解析

addSingletonFactory

? TestA在實(shí)例化之后添加三級(jí)緩存

在這里插入圖片描述

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) 	{
            //添加三級(jí)緩存
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

getSingleton

TestB屬性注入TestA過(guò)程中,從緩存獲取TestA

在這里會(huì)獲取到早期對(duì)象,移除三級(jí)緩存中的ObjectFactory

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從一級(jí)緩存中獲取TestA,這個(gè)時(shí)候是沒(méi)有的
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation 在實(shí)例化之前就添加了創(chuàng)建標(biāo)識(shí),所以這里為true
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        //從二級(jí)緩存中獲取TestA,這個(gè)時(shí)候二級(jí)緩存也是沒(méi)有的
        singletonObject = this.earlySingletonObjects.get(beanName);
        //allowEarlyReference 是否允許循環(huán)依賴
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                //雙重檢查
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        //從三級(jí)緩存中獲取ObjectFactory
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            //獲取早期暴露對(duì)象(代理對(duì)象也是在此生成)
                            singletonObject = singletonFactory.getObject();
                            //添加到二級(jí)緩存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            //根據(jù)beanName從三級(jí)緩存中移除ObjectFactory
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //遍歷smartInstantiationAware類型的后處理器
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            //獲取早期bean引用
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

此時(shí)獲取到的TestA是個(gè)代理對(duì)象,那么TestB里注入的是代理對(duì)象,然后TestA成功注入TestB,但是原本的TestA還是個(gè)普通的對(duì)象,怎么辦呢?

很簡(jiǎn)單,之前不是將對(duì)象放到二級(jí)緩存了嗎,所以在TestA注入完TestB并且初始化之后,這個(gè)時(shí)候會(huì)去二級(jí)緩存中獲取最新的bean,并重新賦值,保證是同一個(gè)對(duì)象,看代碼

//二級(jí)緩存提前暴露
if (earlySingletonExposure) {
    //從二級(jí)緩存中獲取到最新的bean
    Object earlySingletonReference = getSingleton(beanName, false);
    //如果能獲取到并且exposedObject和實(shí)例化之后的bean是保持一致的,那么就進(jìn)行重新賦值
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                    actualDependentBeans.add(dependentBean);
                }
            }
            if (!actualDependentBeans.isEmpty()) {
                throw new BeanCurrentlyInCreationException(beanName,
                                "Bean with name '" + beanName + "' has been injected into other beans [" +
                                    StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                    "] in its raw version as part of a circular reference, but has eventually been " +
                                  "wrapped. This means that said other beans do not use the final version of the " +
                                    "bean. This is often the result of over-eager type matching - consider using " +
                                    "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
        }
    }
}

至此,循環(huán)依賴就解析完畢了,這里有個(gè)需要注意的點(diǎn),就是生成代理對(duì)象那一塊,需要依賴于SmartInstantiationAwareBeanPostProcessor生成代理的,是不能被處理的,例如@Async注解,它需要依賴于SmartInstantiationAwareBeanPostProcessor,被它代理的類,生成代理時(shí)機(jī)是初始化bean之后,那么如果在循環(huán)依賴?yán)锍霈F(xiàn),例如TestA、TestB互相依賴,那么TestA使用了@Async注解,那么它的代理生成時(shí)機(jī)在bean的初始化之后,這樣就會(huì)出現(xiàn)問(wèn)題了,在TestB注入TestA時(shí),從緩存中獲取TestA,這時(shí)是沒(méi)有被代理的,當(dāng)原始的TestA注入完成后,在初始化之后生成代理,這個(gè)時(shí)候就會(huì)造成TestB里注入的TestA不是代理對(duì)象,而原始的TestA已經(jīng)變成代理對(duì)象了,就會(huì)造成不是同一個(gè)對(duì)象

總結(jié)

看完源碼之后,相信大家都有了一些了解,如果看完還是不太明白也沒(méi)關(guān)系,自己跟著debug一遍然后做總結(jié),加深印象,接下來(lái)我們對(duì)以上做一下總結(jié)吧。

如果被問(wèn)到Spring是如何解決循環(huán)依賴的?

答: Spring是通過(guò)三級(jí)緩存去解決的循環(huán)依賴,具體來(lái)說(shuō)就是在TestA實(shí)例化之后,屬性填充之前,把Test包裝成ObjectFactory對(duì)象并存入三級(jí)緩存中,這時(shí)注入TestB,然后在TestB里注入TestA時(shí),就會(huì)從三級(jí)緩存里getObject,取出TestA半成品對(duì)象(如果是代理對(duì)象就進(jìn)行創(chuàng)建),并且配合二級(jí)緩存,把它存入二級(jí)緩存中并在三級(jí)緩存中刪除,最后回到原始TestA,在初始化原TestA之后,進(jìn)行重新賦值,避免不是同一個(gè)對(duì)象。

為什么構(gòu)造器注入不能解決循環(huán)依賴?

**答:**因?yàn)闃?gòu)造器注入是在實(shí)例化bean的時(shí)候,這時(shí)候三級(jí)緩存還沒(méi)有添加,所以不能解決循環(huán)依賴。

為什么要設(shè)計(jì)三級(jí)緩存,一級(jí)、二級(jí)緩存行不行?

這個(gè)問(wèn)題非常值得思考,不過(guò)不要陷入其中,大家可以思考一下

以上就是一文詳解Spring是怎樣處理循環(huán)依賴的的詳細(xì)內(nèi)容,更多關(guān)于Spring處理循環(huán)依賴的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java實(shí)現(xiàn)溫度單位轉(zhuǎn)換(攝氏度和華氏度)

    java實(shí)現(xiàn)溫度單位轉(zhuǎn)換(攝氏度和華氏度)

    在軟件開發(fā)中,溫度轉(zhuǎn)換是測(cè)量與控制系統(tǒng),氣象應(yīng)用,物聯(lián)網(wǎng)終端,科學(xué)計(jì)算等場(chǎng)景的基礎(chǔ)功能之一,所以本文將使用java實(shí)現(xiàn)溫度單位轉(zhuǎn)換功能,需要的可以了解下
    2025-07-07
  • java教學(xué)筆記之對(duì)象的創(chuàng)建與銷毀

    java教學(xué)筆記之對(duì)象的創(chuàng)建與銷毀

    面向?qū)ο蟮木幊陶Z(yǔ)言使程序能夠直觀的反應(yīng)客觀世界的本來(lái)面目,并且使軟件開發(fā)人員能夠運(yùn)用人類認(rèn)識(shí)事物所采用的一般思維方法進(jìn)行軟件開發(fā),是當(dāng)今計(jì)算機(jī)領(lǐng)域中軟件開發(fā)和應(yīng)用的主流技術(shù)。
    2016-01-01
  • 如何將Tomcat容器替換為Jetty容器

    如何將Tomcat容器替換為Jetty容器

    這篇文章主要介紹了如何將Tomcat容器替換為Jetty容器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • Spring MVC 文件上傳的示例代碼

    Spring MVC 文件上傳的示例代碼

    本篇文章主要介紹了Spring MVC 文件上傳的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • SpringCloud Gateway自定義filter獲取body中的數(shù)據(jù)為空的問(wèn)題

    SpringCloud Gateway自定義filter獲取body中的數(shù)據(jù)為空的問(wèn)題

    這篇文章主要介紹了SpringCloud Gateway自定義filter獲取body中的數(shù)據(jù)為空,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Spring使用IOC與DI實(shí)現(xiàn)完全注解開發(fā)

    Spring使用IOC與DI實(shí)現(xiàn)完全注解開發(fā)

    IOC也是Spring的核心之一了,之前學(xué)的時(shí)候是采用xml配置文件的方式去實(shí)現(xiàn)的,后來(lái)其中也多少穿插了幾個(gè)注解,但是沒(méi)有說(shuō)完全采用注解實(shí)現(xiàn)。那么這篇文章就和大家分享一下,全部采用注解來(lái)實(shí)現(xiàn)IOC + DI
    2022-09-09
  • springmvc接口接收參數(shù)與請(qǐng)求參數(shù)格式的整理

    springmvc接口接收參數(shù)與請(qǐng)求參數(shù)格式的整理

    這篇文章主要介紹了springmvc接口接收參數(shù)與請(qǐng)求參數(shù)格式的整理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java的Servlet及其生命周期詳解

    Java的Servlet及其生命周期詳解

    這篇文章主要介紹了Java的Servlet及其生命周期詳解,Servlet是用Java編寫的服務(wù)器端程序,一門用于開發(fā)動(dòng)態(tài)web資源的技術(shù),其主要功能在與交互式的瀏覽和修改數(shù)據(jù),生成動(dòng)態(tài)web內(nèi)容,需要的朋友可以參考下
    2023-11-11
  • IDEA如何修改配置文件的存放位置

    IDEA如何修改配置文件的存放位置

    這篇文章主要介紹了IDEA如何修改配置文件的存放位置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java中@RestController和@Controller的使用區(qū)別解析

    Java中@RestController和@Controller的使用區(qū)別解析

    本文給大家介紹Java中@RestController和@Controller的使用區(qū)別解析,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2025-08-08

最新評(píng)論

丰台区| 清镇市| 保定市| 吐鲁番市| 万载县| 湘潭县| 新干县| 临清市| 丰城市| 九台市| 冀州市| 噶尔县| 峡江县| 麦盖提县| 宜州市| 治多县| 浪卡子县| 淅川县| 河东区| 嘉义市| 古丈县| 台安县| 临湘市| 玉屏| 达州市| 吉林省| 蓬溪县| 西青区| 上犹县| 凤冈县| 沙田区| 宁远县| 东海县| 汕尾市| 黎平县| 元朗区| 永济市| 松溪县| 隆回县| 清远市| 城口县|