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

淺談Spring如何解決循環(huán)依賴的問(wèn)題

 更新時(shí)間:2019年09月10日 10:03:56   作者:愛(ài)寶貝丶  
這篇文章主要介紹了淺談Spring如何解決循環(huán)依賴的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

在關(guān)于Spring的面試中,我們經(jīng)常會(huì)被問(wèn)到一個(gè)問(wèn)題,就是Spring是如何解決循環(huán)依賴的問(wèn)題的。這個(gè)問(wèn)題算是關(guān)于Spring的一個(gè)高頻面試題,因?yàn)槿绻豢桃庋凶x,相信即使讀過(guò)源碼,面試者也不一定能夠一下子思考出個(gè)中奧秘。本文主要針對(duì)這個(gè)問(wèn)題,從源碼的角度對(duì)其實(shí)現(xiàn)原理進(jìn)行講解。

1. 過(guò)程演示

關(guān)于Spring bean的創(chuàng)建,其本質(zhì)上還是一個(gè)對(duì)象的創(chuàng)建,既然是對(duì)象,讀者朋友一定要明白一點(diǎn)就是,一個(gè)完整的對(duì)象包含兩部分:當(dāng)前對(duì)象實(shí)例化和對(duì)象屬性的實(shí)例化。在Spring中,對(duì)象的實(shí)例化是通過(guò)反射實(shí)現(xiàn)的,而對(duì)象的屬性則是在對(duì)象實(shí)例化之后通過(guò)一定的方式設(shè)置的。這個(gè)過(guò)程可以按照如下方式進(jìn)行理解:

理解這一個(gè)點(diǎn)之后,對(duì)于循環(huán)依賴的理解就已經(jīng)幫助一大步了,我們這里以兩個(gè)類A和B為例進(jìn)行講解,如下是A和B的聲明:

@Component
public class A {

 private B b;

 public void setB(B b) {
  this.b = b;
 }
}
@Component
public class B {

 private A a;

 public void setA(A a) {
  this.a = a;
 }
}

可以看到,這里A和B中各自都以對(duì)方為自己的全局屬性。這里首先需要說(shuō)明的一點(diǎn)是,Spring實(shí)例化bean是通過(guò)ApplicationContext.getBean()方法來(lái)進(jìn)行的。如果要獲取的對(duì)象依賴了另一個(gè)對(duì)象,那么其首先會(huì)創(chuàng)建當(dāng)前對(duì)象,然后通過(guò)遞歸的調(diào)用ApplicationContext.getBean()方法來(lái)獲取所依賴的對(duì)象,最后將獲取到的對(duì)象注入到當(dāng)前對(duì)象中。這里我們以上面的首先初始化A對(duì)象實(shí)例為例進(jìn)行講解。首先Spring嘗試通過(guò)ApplicationContext.getBean()方法獲取A對(duì)象的實(shí)例,由于Spring容器中還沒(méi)有A對(duì)象實(shí)例,因而其會(huì)創(chuàng)建一個(gè)A對(duì)象,然后發(fā)現(xiàn)其依賴了B對(duì)象,因而會(huì)嘗試遞歸的通過(guò)ApplicationContext.getBean()方法獲取B對(duì)象的實(shí)例,但是Spring容器中此時(shí)也沒(méi)有B對(duì)象的實(shí)例,因而其還是會(huì)先創(chuàng)建一個(gè)B對(duì)象的實(shí)例。讀者需要注意這個(gè)時(shí)間點(diǎn),此時(shí)A對(duì)象和B對(duì)象都已經(jīng)創(chuàng)建了,并且保存在Spring容器中了,只不過(guò)A對(duì)象的屬性b和B對(duì)象的屬性a都還沒(méi)有設(shè)置進(jìn)去。在前面Spring創(chuàng)建B對(duì)象之后,Spring發(fā)現(xiàn)B對(duì)象依賴了屬性A,因而此時(shí)還是會(huì)嘗試遞歸的調(diào)用ApplicationContext.getBean()方法獲取A對(duì)象的實(shí)例,因?yàn)镾pring中已經(jīng)有一個(gè)A對(duì)象的實(shí)例,雖然只是半成品(其屬性b還未初始化),但其也還是目標(biāo)bean,因而會(huì)將該A對(duì)象的實(shí)例返回。此時(shí),B對(duì)象的屬性a就設(shè)置進(jìn)去了,然后還是ApplicationContext.getBean()方法遞歸的返回,也就是將B對(duì)象的實(shí)例返回,此時(shí)就會(huì)將該實(shí)例設(shè)置到A對(duì)象的屬性b中。這個(gè)時(shí)候,注意A對(duì)象的屬性b和B對(duì)象的屬性a都已經(jīng)設(shè)置了目標(biāo)對(duì)象的實(shí)例了。讀者朋友可能會(huì)比較疑惑的是,前面在為對(duì)象B設(shè)置屬性a的時(shí)候,這個(gè)A類型屬性還是個(gè)半成品。但是需要注意的是,這個(gè)A是一個(gè)引用,其本質(zhì)上還是最開(kāi)始就實(shí)例化的A對(duì)象。而在上面這個(gè)遞歸過(guò)程的最后,Spring將獲取到的B對(duì)象實(shí)例設(shè)置到了A對(duì)象的屬性b中了,這里的A對(duì)象其實(shí)和前面設(shè)置到實(shí)例B中的半成品A對(duì)象是同一個(gè)對(duì)象,其引用地址是同一個(gè),這里為A對(duì)象的b屬性設(shè)置了值,其實(shí)也就是為那個(gè)半成品的a屬性設(shè)置了值。下面我們通過(guò)一個(gè)流程圖來(lái)對(duì)這個(gè)過(guò)程進(jìn)行講解:

圖中getBean()表示調(diào)用Spring的ApplicationContext.getBean()方法,而該方法中的參數(shù),則表示我們要嘗試獲取的目標(biāo)對(duì)象。圖中的黑色箭頭表示一開(kāi)始的方法調(diào)用走向,走到最后,返回了Spring中緩存的A對(duì)象之后,表示遞歸調(diào)用返回了,此時(shí)使用綠色的箭頭表示。從圖中我們可以很清楚的看到,B對(duì)象的a屬性是在第三步中注入的半成品A對(duì)象,而A對(duì)象的b屬性是在第二步中注入的成品B對(duì)象,此時(shí)半成品的A對(duì)象也就變成了成品的A對(duì)象,因?yàn)槠鋵傩砸呀?jīng)設(shè)置完成了。

2. 源碼講解

對(duì)于Spring處理循環(huán)依賴問(wèn)題的方式,我們這里通過(guò)上面的流程圖其實(shí)很容易就可以理解,需要注意的一個(gè)點(diǎn)就是,Spring是如何標(biāo)記開(kāi)始生成的A對(duì)象是一個(gè)半成品,并且是如何保存A對(duì)象的。這里的標(biāo)記工作Spring是使用ApplicationContext的屬性Set<String> singletonsCurrentlyInCreation來(lái)保存的,而半成品的A對(duì)象則是通過(guò)Map<String, ObjectFactory<?>> singletonFactories來(lái)保存的,這里的ObjectFactory是一個(gè)工廠對(duì)象,可通過(guò)調(diào)用其getObject()方法來(lái)獲取目標(biāo)對(duì)象。在AbstractBeanFactory.doGetBean()方法中獲取對(duì)象的方法如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
  @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
 // 嘗試通過(guò)bean名稱獲取目標(biāo)bean對(duì)象,比如這里的A對(duì)象
 Object sharedInstance = getSingleton(beanName);
 
 // 我們這里的目標(biāo)對(duì)象都是單例的
 if (mbd.isSingleton()) {
  // 這里就嘗試創(chuàng)建目標(biāo)對(duì)象,第二個(gè)參數(shù)傳的就是一個(gè)ObjectFactory類型的對(duì)象,這里是使用Java8的lamada
  // 表達(dá)式書(shū)寫(xiě)的,只要上面的getSingleton()方法返回值為空,則會(huì)調(diào)用這里的getSingleton()方法來(lái)創(chuàng)建
  // 目標(biāo)對(duì)象
  sharedInstance = getSingleton(beanName, () -> {
   try {
    // 嘗試創(chuàng)建目標(biāo)對(duì)象
    return createBean(beanName, mbd, args);
   } catch (BeansException ex) {
    throw ex;
   }
  });
 }
 return (T) bean;
}

這里的doGetBean()方法是非常關(guān)鍵的一個(gè)方法(中間省略了其他代碼),上面也主要有兩個(gè)步驟,第一個(gè)步驟的getSingleton()方法的作用是嘗試從緩存中獲取目標(biāo)對(duì)象,如果沒(méi)有獲取到,則嘗試獲取半成品的目標(biāo)對(duì)象;如果第一個(gè)步驟沒(méi)有獲取到目標(biāo)對(duì)象的實(shí)例,那么就進(jìn)入第二個(gè)步驟,第二個(gè)步驟的getSingleton()方法的作用是嘗試創(chuàng)建目標(biāo)對(duì)象,并且為該對(duì)象注入其所依賴的屬性。

這里其實(shí)就是主干邏輯,我們前面圖中已經(jīng)標(biāo)明,在整個(gè)過(guò)程中會(huì)調(diào)用三次doGetBean()方法,第一次調(diào)用的時(shí)候會(huì)嘗試獲取A對(duì)象實(shí)例,此時(shí)走的是第一個(gè)getSingleton()方法,由于沒(méi)有已經(jīng)創(chuàng)建的A對(duì)象的成品或半成品,因而這里得到的是null,然后就會(huì)調(diào)用第二個(gè)getSingleton()方法,創(chuàng)建A對(duì)象的實(shí)例,然后遞歸的調(diào)用doGetBean()方法,嘗試獲取B對(duì)象的實(shí)例以注入到A對(duì)象中,此時(shí)由于Spring容器中也沒(méi)有B對(duì)象的成品或半成品,因而還是會(huì)走到第二個(gè)getSingleton()方法,在該方法中創(chuàng)建B對(duì)象的實(shí)例,創(chuàng)建完成之后,嘗試獲取其所依賴的A的實(shí)例作為其屬性,因而還是會(huì)遞歸的調(diào)用doGetBean()方法,此時(shí)需要注意的是,在前面由于已經(jīng)有了一個(gè)半成品的A對(duì)象的實(shí)例,因而這個(gè)時(shí)候,再嘗試獲取A對(duì)象的實(shí)例的時(shí)候,會(huì)走第一個(gè)getSingleton()方法,在該方法中會(huì)得到一個(gè)半成品的A對(duì)象的實(shí)例。然后將該實(shí)例返回,并且將其注入到B對(duì)象的屬性a中,此時(shí)B對(duì)象實(shí)例化完成。然后將實(shí)例化完成的B對(duì)象遞歸的返回,此時(shí)就會(huì)將該實(shí)例注入到A對(duì)象中,這樣就得到了一個(gè)成品的A對(duì)象。我們這里可以閱讀上面的第一個(gè)getSingleton()方法:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
 // 嘗試從緩存中獲取成品的目標(biāo)對(duì)象,如果存在,則直接返回
 Object singletonObject = this.singletonObjects.get(beanName);
 // 如果緩存中不存在目標(biāo)對(duì)象,則判斷當(dāng)前對(duì)象是否已經(jīng)處于創(chuàng)建過(guò)程中,在前面的講解中,第一次嘗試獲取A對(duì)象
 // 的實(shí)例之后,就會(huì)將A對(duì)象標(biāo)記為正在創(chuàng)建中,因而最后再嘗試獲取A對(duì)象的時(shí)候,這里的if判斷就會(huì)為true
 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  synchronized (this.singletonObjects) {
   singletonObject = this.earlySingletonObjects.get(beanName);
   if (singletonObject == null && allowEarlyReference) {
    // 這里的singletonFactories是一個(gè)Map,其key是bean的名稱,而值是一個(gè)ObjectFactory類型的
    // 對(duì)象,這里對(duì)于A和B而言,調(diào)用圖其getObject()方法返回的就是A和B對(duì)象的實(shí)例,無(wú)論是否是半成品
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
     // 獲取目標(biāo)對(duì)象的實(shí)例
     singletonObject = singletonFactory.getObject();
     this.earlySingletonObjects.put(beanName, singletonObject);
     this.singletonFactories.remove(beanName);
    }
   }
  }
 }
 return singletonObject;
}

這里我們會(huì)存在一個(gè)問(wèn)題就是A的半成品實(shí)例是如何實(shí)例化的,然后是如何將其封裝為一個(gè)ObjectFactory類型的對(duì)象,并且將其放到上面的singletonFactories屬性中的。這主要是在前面的第二個(gè)getSingleton()方法中,其最終會(huì)通過(guò)其傳入的第二個(gè)參數(shù),從而調(diào)用createBean()方法,該方法的最終調(diào)用是委托給了另一個(gè)doCreateBean()方法進(jìn)行的,這里面有如下一段代碼:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
 throws BeanCreationException {

 // 實(shí)例化當(dāng)前嘗試獲取的bean對(duì)象,比如A對(duì)象和B對(duì)象都是在這里實(shí)例化的
 BeanWrapper instanceWrapper = null;
 if (mbd.isSingleton()) {
  instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
 }
 if (instanceWrapper == null) {
  instanceWrapper = createBeanInstance(beanName, mbd, args);
 }
 
 // 判斷Spring是否配置了支持提前暴露目標(biāo)bean,也就是是否支持提前暴露半成品的bean
 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
  && isSingletonCurrentlyInCreation(beanName));
 if (earlySingletonExposure) {
  // 如果支持,這里就會(huì)將當(dāng)前生成的半成品的bean放到singletonFactories中,這個(gè)singletonFactories
  // 就是前面第一個(gè)getSingleton()方法中所使用到的singletonFactories屬性,也就是說(shuō),這里就是
  // 封裝半成品的bean的地方。而這里的getEarlyBeanReference()本質(zhì)上是直接將放入的第三個(gè)參數(shù),也就是
  // 目標(biāo)bean直接返回
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 }

 try {
  // 在初始化實(shí)例之后,這里就是判斷當(dāng)前bean是否依賴了其他的bean,如果依賴了,
  // 就會(huì)遞歸的調(diào)用getBean()方法嘗試獲取目標(biāo)bean
  populateBean(beanName, mbd, instanceWrapper);
 } catch (Throwable ex) {
  // 省略...
 }
 
 return exposedObject;
}

到這里,Spring整個(gè)解決循環(huán)依賴問(wèn)題的實(shí)現(xiàn)思路已經(jīng)比較清楚了。對(duì)于整體過(guò)程,讀者朋友只要理解兩點(diǎn):

  • Spring是通過(guò)遞歸的方式獲取目標(biāo)bean及其所依賴的bean的;
  • Spring實(shí)例化一個(gè)bean的時(shí)候,是分兩步進(jìn)行的,首先實(shí)例化目標(biāo)bean,然后為其注入屬性。

結(jié)合這兩點(diǎn),也就是說(shuō),Spring在實(shí)例化一個(gè)bean的時(shí)候,是首先遞歸的實(shí)例化其所依賴的所有bean,直到某個(gè)bean沒(méi)有依賴其他bean,此時(shí)就會(huì)將該實(shí)例返回,然后反遞歸的將獲取到的bean設(shè)置為各個(gè)上層bean的屬性的。

3. 小結(jié)

本文首先通過(guò)圖文的方式對(duì)Spring是如何解決循環(huán)依賴的問(wèn)題進(jìn)行了講解,然后從源碼的角度詳細(xì)講解了Spring是如何實(shí)現(xiàn)各個(gè)bean的裝配工作的。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java中的CyclicBarrier循環(huán)柵欄解析

    Java中的CyclicBarrier循環(huán)柵欄解析

    這篇文章主要介紹了Java中的CyclicBarrier循環(huán)柵欄解析,從字面上的意思可以知道,這個(gè)類的中文意思是"循環(huán)柵欄",大概的意思就是一個(gè)可循環(huán)利用的屏障,它的作用就是會(huì)讓所有線程都等待完成后才會(huì)繼續(xù)下一步行動(dòng),需要的朋友可以參考下
    2023-12-12
  • Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理)

    Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理)

    這篇文章主要介紹了Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理),分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • IntelliJ IDEA(2019)安裝破解及HelloWorld案例(圖文)

    IntelliJ IDEA(2019)安裝破解及HelloWorld案例(圖文)

    這篇文章主要介紹了IntelliJ IDEA(2019)安裝破解及HelloWorld案例(圖文),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Java在PowerPoint中添加上標(biāo)和下標(biāo)的實(shí)現(xiàn)方法

    Java在PowerPoint中添加上標(biāo)和下標(biāo)的實(shí)現(xiàn)方法

    當(dāng)我們?cè)谘菔疚母逯刑砑由虡?biāo)、版權(quán)或其他符號(hào)時(shí),我們可能希望該符號(hào)出現(xiàn)在某個(gè)文本的上方或下方。在Microsoft PowerPoint中,我們可以通過(guò)對(duì)符號(hào)應(yīng)用上標(biāo)或下標(biāo)格式來(lái)實(shí)現(xiàn)這種效果,這篇文章主要介紹了Java在PowerPoint中添加上標(biāo)和下標(biāo),需要的朋友可以參考下
    2022-10-10
  • java獲取文件擴(kuò)展名的方法小結(jié)【正則與字符串截取】

    java獲取文件擴(kuò)展名的方法小結(jié)【正則與字符串截取】

    這篇文章主要介紹了java獲取文件擴(kuò)展名的方法,結(jié)合實(shí)例形式分析了使用正則與字符串截取兩種獲取擴(kuò)展名的操作技巧,需要的朋友可以參考下
    2017-01-01
  • springboot 集成cas5.3 實(shí)現(xiàn)sso單點(diǎn)登錄詳細(xì)流程

    springboot 集成cas5.3 實(shí)現(xiàn)sso單點(diǎn)登錄詳細(xì)流程

    SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問(wèn)所有相互信任的應(yīng)用系統(tǒng)。單點(diǎn)登錄是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一,本文給大家介紹springboot 集成cas5.3 實(shí)現(xiàn)sso單點(diǎn)登錄功能,感興趣的朋友一起看看吧
    2021-10-10
  • 重新實(shí)現(xiàn)hashCode()方法

    重新實(shí)現(xiàn)hashCode()方法

    hashCode()是Java中的一個(gè)重要方法,用于計(jì)算對(duì)象的哈希碼。本文介紹了如何重新實(shí)現(xiàn)hashCode()方法,包括使用對(duì)象的屬性計(jì)算哈希碼、使用字符串拼接計(jì)算哈希碼、使用隨機(jī)數(shù)計(jì)算哈希碼等方法。同時(shí),還介紹了如何避免哈希沖突,提高哈希表的效率。
    2023-04-04
  • Java解碼H264格式視頻流中的圖片

    Java解碼H264格式視頻流中的圖片

    這篇文章主要為大家詳細(xì)介紹了Java解碼H264格式視頻流中的圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • SpringWebMVC的常用注解及應(yīng)用分層架構(gòu)詳解

    SpringWebMVC的常用注解及應(yīng)用分層架構(gòu)詳解

    這篇文章主要介紹了SpringWebMVC的常用注解及應(yīng)用分層架構(gòu),SpringWebMVC是基于ServletAPI構(gòu)建的原始Web框架,從?開(kāi)始就包含在Spring框架中,感興趣的朋友可以參考下
    2024-05-05
  • 詳解Java的Hibernate框架中的緩存與二級(jí)緩存

    詳解Java的Hibernate框架中的緩存與二級(jí)緩存

    這篇文章主要介紹了Java的Hibernate框架中的緩存與二級(jí)緩存,Hibernate是Java的SSH三大web開(kāi)發(fā)框架之一,需要的朋友可以參考下
    2015-12-12

最新評(píng)論