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

Spring源碼之循環(huán)依賴(lài)之三級(jí)緩存詳解

 更新時(shí)間:2022年02月09日 15:29:44   作者:一定會(huì)去到彩虹海的麥當(dāng)  
這篇文章主要為大家詳細(xì)介紹了Spring源碼之循環(huán)依賴(lài)之三級(jí)緩存,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

循環(huán)依賴(lài)

定義

循環(huán)依賴(lài)就 循環(huán)引用,就是兩個(gè)或多個(gè) bean 相互之間的持有對(duì)方,比如 CircleA 引用 CircleB , CircleB 引用 CircleC, CircleC 引用 CircleA ,則它們最終反映為 個(gè)環(huán)。此處不是循環(huán)調(diào)用,循環(huán)調(diào)用是方法之間的環(huán)調(diào)用。

循環(huán)調(diào)用是無(wú)法解決的,除非有終結(jié)條件,否則就是死循環(huán),最終導(dǎo)致內(nèi)存溢出錯(cuò)誤

三種循環(huán)依賴(lài)的情況

Spring容器將每一個(gè)正在創(chuàng)建的bean標(biāo)識(shí)符放在一個(gè)“當(dāng)前創(chuàng)建bean池”中,bean標(biāo)識(shí)符在創(chuàng)建過(guò)程中將一直保持在這個(gè)池中,因此如果在創(chuàng)建bean過(guò)程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建bean池”里時(shí),將拋出BeanCurrentlylnCreationException異常表示循環(huán)依賴(lài);而對(duì)于創(chuàng)建完畢的bean將從“當(dāng)前創(chuàng)建bean池"中清除掉。

1.構(gòu)造器循環(huán)依賴(lài)

表示通過(guò)構(gòu)造器注入構(gòu)成的循環(huán)依賴(lài),此依賴(lài)是無(wú)法解決的,只能拋出異常

2.settler循環(huán)依賴(lài)

表示通過(guò)setter注入方式構(gòu)成的循環(huán)依賴(lài)。對(duì)于setter注入造成的依賴(lài)是通過(guò)Spring容器提前暴露剛完成構(gòu)造器注入但未完成其他步驟(如setter注人)的bean來(lái)完成的,而且只能解決單例作用域的bean循環(huán)依賴(lài)。通過(guò)提前暴露一個(gè)單例工廠方法,從而使其他bean能引用到該bean。

3.prototype范圍的依賴(lài)處理

對(duì)于"prototype"作用域bean,Spring容器無(wú)法完成依賴(lài)注人,因?yàn)镾pring容器不進(jìn)行緩存"prototype"作用域的bean,因此無(wú)法提前暴露一個(gè)創(chuàng)建中的bean。

三級(jí)緩存機(jī)制

  • 定義 一級(jí)緩存用于存放已經(jīng)實(shí)例化、初始化完成的Bean,單例池-singletonObjects
  • 二級(jí)緩存用于存放已經(jīng)實(shí)例化,但未初始化的Bean.保證一個(gè)類(lèi)多次循環(huán)依賴(lài)時(shí)僅構(gòu)建一次保證單例提前曝光早產(chǎn)bean池-earlySingletonObjects
  • 三級(jí)緩存用于存放該Bean的BeanFactory,當(dāng)加載一個(gè)Bean會(huì)先將該Bean包裝為BeanFactory放入三級(jí)緩存早期單例bean工廠池-singletonFactories
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	/** Cache of singleton objects: bean name --> bean instance */
	//用于存放完全初始化好的 bean從該緩存中取出的 bean可以直接使用
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
	/** Cache of singleton factories: bean name --> ObjectFactory */
	//存放 bean工廠對(duì)象解決循環(huán)依賴(lài)
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
	//存放原始的bean對(duì)象用于解決循環(huán)依賴(lài),注意:存到里面的對(duì)象還沒(méi)有被填充屬性
	/** Cache of early singleton objects: bean name --> bean instance */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
}

三級(jí)緩存

整體分析

創(chuàng)建Bean會(huì)先將該Bean的BeanFactory放到三級(jí)緩存中,以用來(lái)防止循環(huán)依賴(lài)問(wèn)題.當(dāng)存在有A,B兩個(gè)Bean循環(huán)依賴(lài)時(shí),創(chuàng)建流程如下

1.先創(chuàng)建BeanA,先實(shí)例化BeanA并包裝為BeanFactory并放入三級(jí)緩存中.

2.給BeanA進(jìn)行屬性填充時(shí)檢查依賴(lài),發(fā)現(xiàn)BeanB未加載過(guò),則先去加載BeanB

3.BeanB創(chuàng)建過(guò)程首先也要包裝成BeanFactory放到三級(jí)緩存,填充屬性時(shí)則是從三級(jí)緩存獲取Bean將BeanA填充進(jìn)去

4.。BeanB填充BeanA從三級(jí)緩存中的BeanAFacotry獲取BeanA

5.獲取主要通過(guò)ObjectFactory.getObject方法,該方法調(diào)用getEarlyBeanReference方法,他會(huì)創(chuàng)建Bean/Bean的代理并刪除BeanA的三級(jí)緩存,加入二級(jí)緩存

6.BeanB初始化完畢加入一級(jí)緩存,BeanA繼續(xù)執(zhí)行初始化,初始化完畢比較BeanA二級(jí)緩存和一級(jí)緩存是否一致,一致則加入一級(jí)緩存刪除二級(jí)緩存

img

源碼分析

此處以A、B類(lèi)的互相依賴(lài)注入為例,在這里表達(dá)出關(guān)鍵代碼的走勢(shì):

1.入口處即是實(shí)例化、初始化A這個(gè)單例Bean。AbstractBeanFactory.doGetBean("a")

protected <T> T doGetBean(...){
	... 
	// 標(biāo)記beanName a是已經(jīng)創(chuàng)建過(guò)至少一次的~~~ 它會(huì)一直存留在緩存里不會(huì)被移除(除非拋出了異常)
	// 參見(jiàn)緩存Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256))
	if (!typeCheckOnly) {
		markBeanAsCreated(beanName);
	}
	// 此時(shí)a不存在任何一級(jí)緩存中,且不是在創(chuàng)建中  所以此處返回null
	// 此處若不為null,然后從緩存里拿就可以了(主要處理FactoryBean和BeanFactory情況吧)
	Object beanInstance = getSingleton(beanName, false);
	...
	// 這個(gè)getSingleton方法非常關(guān)鍵。
	//1、標(biāo)注a正在創(chuàng)建中~
	//2、調(diào)用singletonObject = singletonFactory.getObject();(實(shí)際上調(diào)用的是createBean()方法)  因此這一步最為關(guān)鍵
	//3、此時(shí)實(shí)例已經(jīng)創(chuàng)建完成  會(huì)把a(bǔ)移除整整創(chuàng)建的緩存中
	//4、執(zhí)行addSingleton()添加進(jìn)去。(備注:注冊(cè)bean的接口方法為registerSingleton,它依賴(lài)于addSingleton方法)
	sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}

下面進(jìn)入到最為復(fù)雜的AbstractAutowireCapableBeanFactory.createBean/doCreateBean()環(huán)節(jié),創(chuàng)建A的實(shí)例

protected Object doCreateBean(){
	...
	// 使用構(gòu)造器/工廠方法   instanceWrapper是一個(gè)BeanWrapper
	instanceWrapper = createBeanInstance(beanName, mbd, args);
	// 此處bean為"原始Bean"   也就是這里的A實(shí)例對(duì)象:A@1234
	final Object bean = instanceWrapper.getWrappedInstance();
	...
	// 是否要提前暴露(允許循環(huán)依賴(lài))  現(xiàn)在此處A是被允許的
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
	// 允許暴露,就把A綁定在ObjectFactory上,注冊(cè)到三級(jí)緩存`singletonFactories`里面去保存著
	// Tips:這里后置處理器的getEarlyBeanReference方法會(huì)被促發(fā),自動(dòng)代理創(chuàng)建器在此處創(chuàng)建代理對(duì)象(注意執(zhí)行時(shí)機(jī) 為執(zhí)行三級(jí)緩存的時(shí)候)
	if (earlySingletonExposure) {
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	...
	// exposedObject 為最終返回的對(duì)象,此處為原始對(duì)象bean也就是A@1234,下面會(huì)有用處
	Object exposedObject = bean; 
	// 給A@1234屬性完成賦值,@Autowired在此處起作用~
	// 因此此處會(huì)調(diào)用getBean("b"),so 會(huì)重復(fù)上面步驟創(chuàng)建B類(lèi)的實(shí)例
	// 此處我們假設(shè)B已經(jīng)創(chuàng)建好了 為B@5678
	// 需要注意的是在populateBean("b")的時(shí)候依賴(lài)有beanA,所以此時(shí)候調(diào)用getBean("a")最終會(huì)調(diào)用getSingleton("a"),
	//此時(shí)候上面說(shuō)到的getEarlyBeanReference方法就會(huì)被執(zhí)行。這也解釋為何我們@Autowired是個(gè)代理對(duì)象,而不是普通對(duì)象的根本原因
	populateBean(beanName, mbd, instanceWrapper);
	// 實(shí)例化。這里會(huì)執(zhí)行后置處理器BeanPostProcessor的兩個(gè)方法
	// 此處注意:postProcessAfterInitialization()是有可能返回一個(gè)代理對(duì)象的,這樣exposedObject 就不再是原始對(duì)象了
	exposedObject = initializeBean(beanName, exposedObject, mbd);
	... // 至此,相當(dāng)于A@1234已經(jīng)實(shí)例化完成、初始化完成(屬性也全部賦值了~)
	// 這一步我把它理解為校驗(yàn):校驗(yàn):校驗(yàn)是否有循環(huán)引用問(wèn)題~~~~~
	if (earlySingletonExposure) {
		// 注意此處第二個(gè)參數(shù)傳的false,表示不去三級(jí)緩存里singletonFactories再去調(diào)用一次getObject()方法了~~~
		// 上面建講到了由于B在初始化的時(shí)候,會(huì)觸發(fā)A的ObjectFactory.getObject()  所以a此處已經(jīng)在二級(jí)緩存earlySingletonObjects里了
		// 因此此處返回A的實(shí)例:A@1234
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			// 這個(gè)等式表示,exposedObject若沒(méi)有再被代理過(guò),這里就是相等的
			// 顯然此處我們的a對(duì)象的exposedObject它是沒(méi)有被代理過(guò)的  所以if會(huì)進(jìn)去~
			// 這種情況至此,就全部結(jié)束了~~~
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
			// 繼續(xù)以A為例,比如方法標(biāo)注了@Aysnc注解,exposedObject此時(shí)候就是一個(gè)代理對(duì)象,因此就會(huì)進(jìn)到這里來(lái)
			//hasDependentBean(beanName)是肯定為true,因?yàn)間etDependentBeans(beanName)得到的是["b"]這個(gè)依賴(lài)
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				// A@1234依賴(lài)的是["b"],所以此處去檢查b
				// 如果最終存在實(shí)際依賴(lài)的bean:actualDependentBeans不為空 那就拋出異常  證明循環(huán)引用了~
				for (String dependentBean : dependentBeans) {
					// 這個(gè)判斷原則是:如果此時(shí)候b并還沒(méi)有創(chuàng)建好,this.alreadyCreated.contains(beanName)=true表示此bean已經(jīng)被創(chuàng)建過(guò),就返回false
					// 若該bean沒(méi)有在alreadyCreated緩存里,就是說(shuō)沒(méi)被創(chuàng)建過(guò)(其實(shí)只有CreatedForTypeCheckOnly才會(huì)是此倉(cāng)庫(kù))
					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 " +
							"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
				}
			}
		}
	}
}

三級(jí)緩存加載bean流程圖

依舊以上面A、B類(lèi)使用屬性field注入循環(huán)依賴(lài)的例子為例,對(duì)整個(gè)流程做文字步驟總結(jié)如下:

1.使用context.getBean(A.class),旨在獲取容器內(nèi)的單例A(若A不存在,就會(huì)走A這個(gè)Bean的創(chuàng)建流程),顯然初次獲取A是不存在的,因此走A的創(chuàng)建之路~

2.實(shí)例化A(注意此處僅僅是實(shí)例化),并將它放進(jìn)緩存(此時(shí)A已經(jīng)實(shí)例化完成,已經(jīng)可以被引用了)

3.初始化A:@Autowired依賴(lài)注入B(此時(shí)需要去容器內(nèi)獲取B)

4.為了完成依賴(lài)注入B,會(huì)通過(guò)getBean(B)去容器內(nèi)找B。但此時(shí)B在容器內(nèi)不存在,就走向B的創(chuàng)建之路~

5.實(shí)例化B,并將其放入緩存。(此時(shí)B也能夠被引用了)

6.初始化B,@Autowired依賴(lài)注入A(此時(shí)需要去容器內(nèi)獲取A)

7.此處重要:初始化B時(shí)會(huì)調(diào)用getBean(A)去容器內(nèi)找到A,上面我們已經(jīng)說(shuō)過(guò)了此時(shí)候因?yàn)锳已經(jīng)實(shí)例化完成了并且放進(jìn)了緩存里,所以這個(gè)時(shí)候去看緩存里是已經(jīng)存在A的引用了的,所以getBean(A)能夠正常返回

8.B初始化成功(此時(shí)已經(jīng)注入A成功了,已成功持有A的引用了),return(注意此處return相當(dāng)于是返回最上面的getBean(B)這句代碼,回到了初始化A的流程中~)。

9.因?yàn)锽實(shí)例已經(jīng)成功返回了,因此最終A也初始化成功

10.到此,B持有的已經(jīng)是初始化完成的A,A持有的也是初始化完成的B

面試題

Spring為什么定三級(jí)緩存(二級(jí)緩存也可以解決循環(huán)依賴(lài)的問(wèn)題)

首先當(dāng)Bean未有循環(huán)依賴(lài)三級(jí)緩存是沒(méi)有什么意義的,當(dāng)有循環(huán)依賴(lài)但Bean并沒(méi)有AOP代理,則會(huì)直接返回原對(duì)象,也沒(méi)有什么意義。主要在當(dāng)Bean存在循環(huán)依賴(lài)并且還有AOP代理時(shí),三級(jí)緩存才有效果,三級(jí)緩存主要預(yù)防Bean有依賴(lài)時(shí)還可以完成代理增強(qiáng)

而本身Spring設(shè)計(jì)Bean的代理增強(qiáng)是在Bean初始化完成后的AnnotationAwareAspectJ***Creator后置處理器中完成的。提前執(zhí)行則和設(shè)計(jì)思路相違背。所以三級(jí)緩存主要起預(yù)防循環(huán)依賴(lài)作用,可能是一個(gè)補(bǔ)丁機(jī)制

參考鏈接:

你知道怎么用Spring的三級(jí)緩存解決循環(huán)依賴(lài)嗎

總結(jié)

本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!   

相關(guān)文章

  • Mysql中的聚簇索引cluster index解析

    Mysql中的聚簇索引cluster index解析

    這篇文章主要介紹了Mysql中的聚簇索引cluster index解析,聚簇索引是一種數(shù)據(jù)庫(kù)索引的類(lèi)型,它將數(shù)據(jù)行物理上存儲(chǔ)在磁盤(pán)上按照索引的順序進(jìn)行排序,聚簇索引可以提高查詢性能,因?yàn)樗梢詼p少磁盤(pán)I/O操作,需要的朋友可以參考下
    2023-10-10
  • Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換詳解

    Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換詳解

    這篇文章主要為大家詳細(xì)介紹了Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • SpringBoot使用Thymeleaf自定義標(biāo)簽的實(shí)例代碼

    SpringBoot使用Thymeleaf自定義標(biāo)簽的實(shí)例代碼

    這篇文章主要介紹了SpringBoot使用Thymeleaf自定義標(biāo)簽的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • Java中的Native關(guān)鍵字講解

    Java中的Native關(guān)鍵字講解

    本文介紹了Java中的Native關(guān)鍵字,native關(guān)鍵字是架起本機(jī)語(yǔ)言和JAVA之間鴻溝的橋梁。如果我們的軟件與硬件的交互在使用預(yù)先存在的代碼時(shí)更有效,那么這可以作為一個(gè)關(guān)鍵環(huán)節(jié)。與從頭開(kāi)始設(shè)計(jì)新的應(yīng)用程序代碼相比,只要可以避免,它就可以使實(shí)現(xiàn)工作更少,下面來(lái)了解集體內(nèi)容
    2021-12-12
  • springboot解決前后端分離時(shí)的跨域問(wèn)題

    springboot解決前后端分離時(shí)的跨域問(wèn)題

    這篇文章主要介紹了springboot如何解決前后端分離時(shí)的跨域問(wèn)題,幫助大家更好的理解和學(xué)習(xí)使用springboot,感興趣的朋友可以了解下
    2021-04-04
  • Springboot如何通過(guò)自定義工具類(lèi)獲取bean

    Springboot如何通過(guò)自定義工具類(lèi)獲取bean

    這篇文章主要介紹了Springboot通過(guò)自定義工具類(lèi)獲取bean方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring Cloud @RefreshScope 原理及使用

    Spring Cloud @RefreshScope 原理及使用

    這篇文章主要介紹了Spring Cloud @RefreshScope 原理及使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • 總結(jié)Java調(diào)用Python程序方法

    總結(jié)Java調(diào)用Python程序方法

    這篇文章主要介紹了總結(jié)Java調(diào)用Python程序方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • SpringBoot整合MongoDB實(shí)現(xiàn)事務(wù)管理

    SpringBoot整合MongoDB實(shí)現(xiàn)事務(wù)管理

    Spring Boot是一種快速開(kāi)發(fā)Spring應(yīng)用的方式,它提供了大量的自動(dòng)配置和默認(rèn)設(shè)置,以簡(jiǎn)化開(kāi)發(fā)流程,MongoDB是一個(gè)基于文檔的NoSQL數(shù)據(jù)庫(kù),本文將介紹如何在Spring Boot應(yīng)用中整合MongoDB,并實(shí)現(xiàn)事務(wù)管理,需要的朋友可以參考下
    2024-07-07
  • 基于Java實(shí)現(xiàn)中文分詞系統(tǒng)的示例代碼

    基于Java實(shí)現(xiàn)中文分詞系統(tǒng)的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)易的中文分詞系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下
    2022-07-07

最新評(píng)論

嘉鱼县| 临猗县| 连云港市| 玉山县| 田林县| 吴堡县| 马鞍山市| 贺州市| 临高县| 孟州市| 霍州市| 樟树市| 临猗县| 弥渡县| 凤凰县| 政和县| 南昌县| 泊头市| 兰州市| 襄城县| 喀喇| 黎平县| 尼玛县| 浦北县| 响水县| 遂昌县| 嘉义市| 晋江市| 泊头市| 德阳市| 西平县| 陵川县| 察雅县| 五莲县| 昭觉县| 民权县| 永川市| 阿拉尔市| 息烽县| 秀山| 五河县|