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

Spring如何解決循環(huán)依賴(lài)的問(wèn)題

 更新時(shí)間:2020年08月26日 08:49:53   作者:紀(jì)莫  
這篇文章主要介紹了Spring是如何解決循環(huán)依賴(lài)的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

前言

在面試的時(shí)候這兩年有一個(gè)非常高頻的關(guān)于spring的問(wèn)題,那就是spring是如何解決循環(huán)依賴(lài)的。這個(gè)問(wèn)題聽(tīng)著就是輕描淡寫(xiě)的一句話,其實(shí)考察的內(nèi)容還是非常多的,主要還是考察的應(yīng)聘者有沒(méi)有研究過(guò)spring的源碼。但是說(shuō)實(shí)話,spring的源碼其實(shí)非常復(fù)雜的,研究起來(lái)并不是個(gè)簡(jiǎn)單的事情,所以我們此篇文章只是為了解釋清楚Spring是如何解決循環(huán)依賴(lài)的這個(gè)問(wèn)題。

什么樣的依賴(lài)算是循環(huán)依賴(lài)?

用過(guò)Spring框架的人都對(duì)依賴(lài)注入這個(gè)詞不陌生,一個(gè)Java類(lèi)A中存在一個(gè)屬性是類(lèi)B的一個(gè)對(duì)象,那么我們就說(shuō)類(lèi)A的對(duì)象依賴(lài)類(lèi)B,而在Spring中是依靠的IOC來(lái)實(shí)現(xiàn)的對(duì)象注入,也就是說(shuō)創(chuàng)建對(duì)象的過(guò)程是IOC容器來(lái)實(shí)現(xiàn)的,并不需要自己在使用的時(shí)候通過(guò)new關(guān)鍵字來(lái)創(chuàng)建對(duì)象。
那么當(dāng)類(lèi)A中依賴(lài)類(lèi)B的對(duì)象,而類(lèi)B中又依賴(lài)類(lèi)C的對(duì)象,最后類(lèi)C中又依賴(lài)類(lèi)A的對(duì)象的時(shí)候,這種情況最終的依賴(lài)關(guān)系會(huì)形成一個(gè)環(huán),這就是循環(huán)依賴(lài)。

循環(huán)依賴(lài)的類(lèi)型

根據(jù)注入的時(shí)機(jī)可以分為兩種:

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

依賴(lài)的對(duì)象是通過(guò)構(gòu)造方法傳入的,在實(shí)例化bean的時(shí)候發(fā)生。

賦值屬性循環(huán)依賴(lài)

依賴(lài)的對(duì)象是通過(guò)setter方法傳入的,對(duì)象已經(jīng)實(shí)例化,在屬性賦值和依賴(lài)注入的時(shí)候發(fā)生。
構(gòu)造器循環(huán)依賴(lài),本質(zhì)上是無(wú)解的,實(shí)例化A的時(shí)候調(diào)用A的構(gòu)造器,發(fā)現(xiàn)依賴(lài)了B,又去實(shí)例化B,然后調(diào)用B的構(gòu)造器,發(fā)現(xiàn)又依賴(lài)的C,然后調(diào)用C的構(gòu)造器去實(shí)例化,結(jié)果發(fā)起C的構(gòu)造器里依賴(lài)了A,這就是個(gè)死循環(huán)無(wú)解。所以Spring也是不支持構(gòu)造器循環(huán)依賴(lài)的,當(dāng)發(fā)現(xiàn)存在構(gòu)造器循環(huán)依賴(lài)時(shí),會(huì)直接拋出BeanCurrentlyInCreationException 異常。
賦值屬性循環(huán)依賴(lài),Spring只支持bean在單例模式下的循環(huán)依賴(lài),其他模式下的循環(huán)依賴(lài)Spring也是會(huì)拋出BeanCurrentlyInCreationException 異常的。Spring通過(guò)對(duì)還在創(chuàng)建過(guò)程中的單例bean,進(jìn)行緩存并提前暴露該單例,使得其他實(shí)例可以提前引用到該單例bean。

Spring為什么只支持單例模式下的bean的賦值情況下的循環(huán)依賴(lài)

在prototype的模式下的bean,使用了一個(gè)ThreadLocal變量prototypesCurrentlyInCreation來(lái)記錄當(dāng)前線程正在創(chuàng)建中的bean,這個(gè)變量在AbtractBeanFactory類(lèi)里。在創(chuàng)建前用beanName記錄bean,在創(chuàng)建完成后刪除bean。在prototypesCurrentlyInCreation里采用了一個(gè)Set對(duì)象來(lái)存儲(chǔ)正在創(chuàng)建中的bean。我們都知道Set是不允許存在重復(fù)對(duì)象的,這樣就能保證同一個(gè)bean在一個(gè)線程中只能有一個(gè)正在創(chuàng)建。
下面是prototypesCurrentlyInCreation變量在刪除bean時(shí)的操作,在AbtractBeanFactorybeforePrototypeCreation操作里。

protected void afterPrototypeCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  if (curVal instanceof String) {
   this.prototypesCurrentlyInCreation.remove();
  }
  else if (curVal instanceof Set) {
   Set<String> beanNameSet = (Set<String>) curVal;
   beanNameSet.remove(beanName);
   if (beanNameSet.isEmpty()) {
    this.prototypesCurrentlyInCreation.remove();
   }
  }
 }

從上面的代碼中看出,當(dāng)變量為一個(gè)的時(shí)候采用了一個(gè)String對(duì)象來(lái)存儲(chǔ),節(jié)省了一些內(nèi)存空間。
AbstractBeanFactory類(lèi)的doGetBean方法里先判斷是否為單例對(duì)象,不是單例對(duì)象,則直接判斷當(dāng)前線程是否已經(jīng)存在了正在創(chuàng)建的bean。存在的話直接拋出異常。

這個(gè)isPrototypeCurrentlyInCreation()方法的實(shí)現(xiàn)代碼如下:

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  return curVal != null && (curVal.equals(beanName) || curVal instanceof Set && ((Set)curVal).contains(beanName));
 }

因?yàn)橛辛诉@個(gè)機(jī)制,spring在原型模式下是解決不了bean的循環(huán)依賴(lài)的,當(dāng)發(fā)現(xiàn)有循環(huán)依賴(lài)的時(shí)候會(huì)直接拋出BeanCurrentlyInCreationException異常的。

那么為什么spring在單例模式下的構(gòu)造賦值也不支持循環(huán)依賴(lài)呢?

其實(shí)原理和原型模式下的情況類(lèi)似,在單例模式下,bean也會(huì)用一個(gè)Set集合來(lái)保存正在創(chuàng)建中的bean,在創(chuàng)建前保存,創(chuàng)建完成后刪除。
這個(gè)對(duì)象在DefaultSingletonBeanRegistry類(lèi)下變量名為:singletonsCurrentlyInCreation

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	 private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
}

判定代碼在DefaultSingletonBeanRegistry類(lèi)的beforeSingletonCreation方法下。

protected void beforeSingletonCreation(String beanName) {
 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
  throw new BeanCurrentlyInCreationException(beanName);
 }
}

在上面這個(gè)方法中,判定singletonsCurrentlyInCreation是否能成功的保存一個(gè)單例bean。如果不能成功保存,那么就會(huì)直接拋出BeanCurrentlyInCreationException異常。

單例模式下的Setter賦值循環(huán)依賴(lài)

終于到了我們的重點(diǎn),Spring是如何解決單例模式下的Setter賦值的循環(huán)依賴(lài)了。
其實(shí)主要的就是靠提前暴露創(chuàng)建中的單例實(shí)例。
那么具體是一個(gè)怎樣的過(guò)程呢?
例如:上面那個(gè)圖的例子,A依賴(lài)B,B依賴(lài)C,C又依賴(lài)B。
過(guò)程如下:

  • 創(chuàng)建A,調(diào)用構(gòu)造方法,完成構(gòu)造,進(jìn)行屬性賦值注入,發(fā)現(xiàn)依賴(lài)B,去實(shí)例化B。
  • 創(chuàng)建B,調(diào)用構(gòu)造方法,完成構(gòu)造,進(jìn)行屬性賦值注入,發(fā)現(xiàn)依賴(lài)C,去實(shí)例化C。

創(chuàng)建C,調(diào)用構(gòu)造方法,完成構(gòu)造,進(jìn)行屬性賦值注入,發(fā)現(xiàn)依賴(lài)A。
這個(gè)時(shí)候就是解決循環(huán)依賴(lài)的關(guān)鍵了,因?yàn)锳已經(jīng)通過(guò)構(gòu)造方法已經(jīng)構(gòu)造完成了,也就是說(shuō)已經(jīng)將Bean的在堆中分配好了內(nèi)存,這樣即使A再填充屬性值也不會(huì)更改內(nèi)存地址了,所以此時(shí)可以提前拿出來(lái)A的引用,來(lái)完成C的實(shí)例化。

這樣上面創(chuàng)建C過(guò)程就會(huì)變成了:

  • 創(chuàng)建C,調(diào)用構(gòu)造方法,完成構(gòu)造,進(jìn)行屬性賦值注入,發(fā)現(xiàn)依賴(lài)A,A已經(jīng)構(gòu)造完成,直接引用,完成C的實(shí)例化。
  • C完成實(shí)例化后,注入B,B也完成了實(shí)例化,然后B注入A,A也完成了實(shí)例化。

為了能獲取到創(chuàng)建中單例bean,spring提供了三級(jí)緩存來(lái)將正在創(chuàng)建中的bean提前暴露。
在類(lèi)DefaultSingletonBeanRegistry下,即下圖紅框中的三個(gè)Map對(duì)象。

這三個(gè)緩存Map的作用如下:

  • 一級(jí)緩存,singletonObjects 單例緩存,存儲(chǔ)已經(jīng)實(shí)例化的單例bean。
  • 二級(jí)緩存,earlySingletonObjects 提前暴露的單例緩存,這里存儲(chǔ)的bean是剛剛構(gòu)造完成,但還會(huì)通過(guò)屬性注入bean。
  • 三級(jí)緩存,singletonFactories 生產(chǎn)單例的工廠緩存,存儲(chǔ)工廠。

首先在創(chuàng)建bean的時(shí)候會(huì)先創(chuàng)建一個(gè)和bean同名的單例工廠,并將bean先放入到單例工廠中。代碼在AbstractAutowireCapableBeanFactory類(lèi)的doCreateBean方法中。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
	......
	this.addSingletonFactory(beanName, new ObjectFactory<Object>() {
  public Object getObject() throws BeansException {
   return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
  }
 });
	.....
}

而上面的代碼中的addSingletonFactory方法的代碼如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
 Assert.notNull(singletonFactory, "Singleton factory must not be null");
 Map var3 = this.singletonObjects;
 synchronized(this.singletonObjects) {
  if (!this.singletonObjects.containsKey(beanName)) {
   this.singletonFactories.put(beanName, singletonFactory);
   this.earlySingletonObjects.remove(beanName);
   this.registeredSingletons.add(beanName);
  }

 }
}

addSingletonFactory方法的作用通過(guò)代碼就可以看到是將存在了正在創(chuàng)建中的bean的單例工廠,放在三級(jí)緩存里,這樣保證了在循環(huán)依賴(lài)查找的時(shí)候是可以找到bean的引用的。
具體讀取緩存獲取bean的過(guò)程在類(lèi)DefaultSingletonBeanRegistrygetSingleton方法里。
如下源碼:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
   Map var4 = this.singletonObjects;
   synchronized(this.singletonObjects) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null && allowEarlyReference) {
     ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
     if (singletonFactory != null) {
      singletonObject = singletonFactory.getObject();
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
     }
    }
   }
  }

  return singletonObject != NULL_OBJECT ? singletonObject : null;
 }

通過(guò)上面的源碼我們可以看到,在獲取單例Bean的時(shí)候,會(huì)先從一級(jí)緩存singletonObjects里獲取,如果沒(méi)有獲取到(說(shuō)明不存在或沒(méi)有實(shí)例化完成),會(huì)去第二級(jí)緩存earlySingletonObjects中去找,如果還是沒(méi)有找到的話,就會(huì)三級(jí)緩存中獲取單例工廠singletonFactory,通過(guò)從singletonFactory中獲取正在創(chuàng)建中的引用,將singletonFactory存儲(chǔ)在earlySingletonObjects 二級(jí)緩存中,這樣就將創(chuàng)建中的單例引用從三級(jí)緩存中升級(jí)到了二級(jí)緩存中,二級(jí)緩存earlySingletonObjects,是會(huì)提前暴露已完成構(gòu)造,還可以執(zhí)行屬性注入的單例bean的。
這個(gè)時(shí)候如何還有其他的bean也是需要屬性注入,那么就可以直接從earlySingletonObjects中獲取了。

上面的例子中的過(guò)程中的A,在注入C的時(shí)候,其實(shí)并沒(méi)有真正的初始化完成,等到順利的注入了B才算是真正的初始化完成。
整個(gè)過(guò)程如下圖:

總結(jié)

到此這篇關(guān)于Spring如何解決循環(huán)依賴(lài)的問(wèn)題的文章就介紹到這了,更多相關(guān)Spring解決循環(huán)依賴(lài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java之map的常見(jiàn)用法講解與五種循環(huán)遍歷實(shí)例代碼理解

    Java之map的常見(jiàn)用法講解與五種循環(huán)遍歷實(shí)例代碼理解

    map是一組鍵值對(duì)的組合,通俗理解類(lèi)似一種特殊的數(shù)組,a[key]=val,只不過(guò)數(shù)組元素的下標(biāo)是任意一種類(lèi)型,而且數(shù)組的元素的值也是任意一種類(lèi)型。有點(diǎn)類(lèi)似python中的字典。通過(guò)"鍵"來(lái)取值,類(lèi)似生活中的字典,已知索引,來(lái)查看對(duì)應(yīng)的信息
    2021-09-09
  • SpringBoot+Docker+IDEA實(shí)現(xiàn)一鍵構(gòu)建+推送、運(yùn)行、同鏡像多容器啟動(dòng)

    SpringBoot+Docker+IDEA實(shí)現(xiàn)一鍵構(gòu)建+推送、運(yùn)行、同鏡像多容器啟動(dòng)

    這篇文章主要介紹了SpringBoot+Docker+IDEA實(shí)現(xiàn)一鍵構(gòu)建+推送、運(yùn)行、同鏡像多容器啟動(dòng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Springboot 在普通類(lèi)型注入Service或mapper

    Springboot 在普通類(lèi)型注入Service或mapper

    這篇文章主要介紹了Springboot 在普通類(lèi)型注入Service或mapper,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 深入剖析Java中String類(lèi)的concat方法

    深入剖析Java中String類(lèi)的concat方法

    這篇文章主要介紹了Java中String類(lèi)的concat方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • IDEA 2020代碼提示忽略大小寫(xiě)的問(wèn)題

    IDEA 2020代碼提示忽略大小寫(xiě)的問(wèn)題

    這篇文章主要介紹了IDEA 2020代碼提示忽略大小寫(xiě)的問(wèn)題,本文通過(guò)圖文并茂的形式給大家分享解決方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 如何通過(guò)Java監(jiān)聽(tīng)MySQL數(shù)據(jù)的變化

    如何通過(guò)Java監(jiān)聽(tīng)MySQL數(shù)據(jù)的變化

    對(duì)于二次開(kāi)發(fā)來(lái)說(shuō),很大一部分就找找文件和找數(shù)據(jù)庫(kù)的變化情況,下面這篇文章主要給大家介紹了關(guān)于如何通過(guò)Java監(jiān)聽(tīng)MySQL數(shù)據(jù)的變化的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • 總結(jié)Java常用的時(shí)間相關(guān)轉(zhuǎn)化

    總結(jié)Java常用的時(shí)間相關(guān)轉(zhuǎn)化

    今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著Java常用的時(shí)間相關(guān)轉(zhuǎn)化展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Java原生方法實(shí)現(xiàn) AES 算法示例

    Java原生方法實(shí)現(xiàn) AES 算法示例

    這篇文章主要介紹了Java原生方法實(shí)現(xiàn) AES 算法,結(jié)合實(shí)例形式分析了Java實(shí)現(xiàn)AES加密算法的相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • Java實(shí)現(xiàn)時(shí)間日期格式轉(zhuǎn)換示例

    Java實(shí)現(xiàn)時(shí)間日期格式轉(zhuǎn)換示例

    本篇文章主要介紹了ava實(shí)現(xiàn)時(shí)間日期格式轉(zhuǎn)換示例,實(shí)現(xiàn)了各種時(shí)間輸出的類(lèi)型,有興趣的可以了解一下。
    2017-01-01
  • Java中對(duì)象的比較操作實(shí)例分析

    Java中對(duì)象的比較操作實(shí)例分析

    這篇文章主要介紹了Java中對(duì)象的比較操作,結(jié)合實(shí)例形式分析了java對(duì)象比較操作實(shí)現(xiàn)方法與相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2019-08-08

最新評(píng)論