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

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

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

環(huán)境

Spring Framework Version: 5.3.x

Gradle Version:7.5.1

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

簡單理解就是A,B 兩個bean相互依賴,A依賴B,B依賴A

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

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

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

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

場景

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

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

//使用的注入方式是自動裝配,根據type自動裝配
<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();
    }
}

測試結果

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

好,代碼編寫完畢,測試也沒問題,接下來我們來進行源碼解析

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

前置說明

Spring是通過三級緩存來解決循環(huán)依賴的,我們來看一張圖片,認識一下三級緩存

在這里插入圖片描述

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

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

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

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

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

這么說可能有點繞,我們結合一張圖來看看

在這里插入圖片描述

大概就是這樣

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

源碼解析

addSingletonFactory

? TestA在實例化之后添加三級緩存

在這里插入圖片描述

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

getSingleton

TestB屬性注入TestA過程中,從緩存獲取TestA

在這里會獲取到早期對象,移除三級緩存中的ObjectFactory

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從一級緩存中獲取TestA,這個時候是沒有的
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation 在實例化之前就添加了創(chuàng)建標識,所以這里為true
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        //從二級緩存中獲取TestA,這個時候二級緩存也是沒有的
        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) {
                        //從三級緩存中獲取ObjectFactory
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            //獲取早期暴露對象(代理對象也是在此生成)
                            singletonObject = singletonFactory.getObject();
                            //添加到二級緩存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            //根據beanName從三級緩存中移除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;
}

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

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

//二級緩存提前暴露
if (earlySingletonExposure) {
    //從二級緩存中獲取到最新的bean
    Object earlySingletonReference = getSingleton(beanName, false);
    //如果能獲取到并且exposedObject和實例化之后的bean是保持一致的,那么就進行重新賦值
    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)依賴就解析完畢了,這里有個需要注意的點,就是生成代理對象那一塊,需要依賴于SmartInstantiationAwareBeanPostProcessor生成代理的,是不能被處理的,例如@Async注解,它需要依賴于SmartInstantiationAwareBeanPostProcessor,被它代理的類,生成代理時機是初始化bean之后,那么如果在循環(huán)依賴里出現,例如TestA、TestB互相依賴,那么TestA使用了@Async注解,那么它的代理生成時機在bean的初始化之后,這樣就會出現問題了,在TestB注入TestA時,從緩存中獲取TestA,這時是沒有被代理的,當原始的TestA注入完成后,在初始化之后生成代理,這個時候就會造成TestB里注入的TestA不是代理對象,而原始的TestA已經變成代理對象了,就會造成不是同一個對象

總結

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

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

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

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

**答:**因為構造器注入是在實例化bean的時候,這時候三級緩存還沒有添加,所以不能解決循環(huán)依賴。

為什么要設計三級緩存,一級、二級緩存行不行?

這個問題非常值得思考,不過不要陷入其中,大家可以思考一下

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

相關文章

  • java實現溫度單位轉換(攝氏度和華氏度)

    java實現溫度單位轉換(攝氏度和華氏度)

    在軟件開發(fā)中,溫度轉換是測量與控制系統,氣象應用,物聯網終端,科學計算等場景的基礎功能之一,所以本文將使用java實現溫度單位轉換功能,需要的可以了解下
    2025-07-07
  • java教學筆記之對象的創(chuàng)建與銷毀

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

    面向對象的編程語言使程序能夠直觀的反應客觀世界的本來面目,并且使軟件開發(fā)人員能夠運用人類認識事物所采用的一般思維方法進行軟件開發(fā),是當今計算機領域中軟件開發(fā)和應用的主流技術。
    2016-01-01
  • 如何將Tomcat容器替換為Jetty容器

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

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

    Spring MVC 文件上傳的示例代碼

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

    SpringCloud Gateway自定義filter獲取body中的數據為空的問題

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

    Spring使用IOC與DI實現完全注解開發(fā)

    IOC也是Spring的核心之一了,之前學的時候是采用xml配置文件的方式去實現的,后來其中也多少穿插了幾個注解,但是沒有說完全采用注解實現。那么這篇文章就和大家分享一下,全部采用注解來實現IOC + DI
    2022-09-09
  • springmvc接口接收參數與請求參數格式的整理

    springmvc接口接收參數與請求參數格式的整理

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

    Java的Servlet及其生命周期詳解

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

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

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

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

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

最新評論

大悟县| 神池县| 体育| 乐都县| 江油市| 台南县| 来宾市| 额济纳旗| 成武县| 拜泉县| 资阳市| 中宁县| 昔阳县| 桐城市| 彰武县| 萝北县| 邢台市| 三都| 久治县| 长沙市| 南康市| 东阿县| 吴川市| 新余市| 蓝山县| 霍林郭勒市| 台中县| 壤塘县| 成安县| 邢台县| 樟树市| 梓潼县| 于田县| 建阳市| 临澧县| 海原县| 偏关县| 当涂县| 永城市| 济源市| 陆丰市|