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

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

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

在關于Spring的面試中,我們經(jīng)常會被問到一個問題,就是Spring是如何解決循環(huán)依賴的問題的。這個問題算是關于Spring的一個高頻面試題,因為如果不刻意研讀,相信即使讀過源碼,面試者也不一定能夠一下子思考出個中奧秘。本文主要針對這個問題,從源碼的角度對其實現(xiàn)原理進行講解。

1. 過程演示

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

理解這一個點之后,對于循環(huán)依賴的理解就已經(jīng)幫助一大步了,我們這里以兩個類A和B為例進行講解,如下是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中各自都以對方為自己的全局屬性。這里首先需要說明的一點是,Spring實例化bean是通過ApplicationContext.getBean()方法來進行的。如果要獲取的對象依賴了另一個對象,那么其首先會創(chuàng)建當前對象,然后通過遞歸的調用ApplicationContext.getBean()方法來獲取所依賴的對象,最后將獲取到的對象注入到當前對象中。這里我們以上面的首先初始化A對象實例為例進行講解。首先Spring嘗試通過ApplicationContext.getBean()方法獲取A對象的實例,由于Spring容器中還沒有A對象實例,因而其會創(chuàng)建一個A對象,然后發(fā)現(xiàn)其依賴了B對象,因而會嘗試遞歸的通過ApplicationContext.getBean()方法獲取B對象的實例,但是Spring容器中此時也沒有B對象的實例,因而其還是會先創(chuàng)建一個B對象的實例。讀者需要注意這個時間點,此時A對象和B對象都已經(jīng)創(chuàng)建了,并且保存在Spring容器中了,只不過A對象的屬性b和B對象的屬性a都還沒有設置進去。在前面Spring創(chuàng)建B對象之后,Spring發(fā)現(xiàn)B對象依賴了屬性A,因而此時還是會嘗試遞歸的調用ApplicationContext.getBean()方法獲取A對象的實例,因為Spring中已經(jīng)有一個A對象的實例,雖然只是半成品(其屬性b還未初始化),但其也還是目標bean,因而會將該A對象的實例返回。此時,B對象的屬性a就設置進去了,然后還是ApplicationContext.getBean()方法遞歸的返回,也就是將B對象的實例返回,此時就會將該實例設置到A對象的屬性b中。這個時候,注意A對象的屬性b和B對象的屬性a都已經(jīng)設置了目標對象的實例了。讀者朋友可能會比較疑惑的是,前面在為對象B設置屬性a的時候,這個A類型屬性還是個半成品。但是需要注意的是,這個A是一個引用,其本質上還是最開始就實例化的A對象。而在上面這個遞歸過程的最后,Spring將獲取到的B對象實例設置到了A對象的屬性b中了,這里的A對象其實和前面設置到實例B中的半成品A對象是同一個對象,其引用地址是同一個,這里為A對象的b屬性設置了值,其實也就是為那個半成品的a屬性設置了值。下面我們通過一個流程圖來對這個過程進行講解:

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

2. 源碼講解

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

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

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

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

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

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

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

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

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

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

  • Spring是通過遞歸的方式獲取目標bean及其所依賴的bean的;
  • Spring實例化一個bean的時候,是分兩步進行的,首先實例化目標bean,然后為其注入屬性。

結合這兩點,也就是說,Spring在實例化一個bean的時候,是首先遞歸的實例化其所依賴的所有bean,直到某個bean沒有依賴其他bean,此時就會將該實例返回,然后反遞歸的將獲取到的bean設置為各個上層bean的屬性的。

3. 小結

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Java控制臺輸入scanner的方法及代碼詳解

    Java控制臺輸入scanner的方法及代碼詳解

    在本篇內容里小編給大家分享的是一篇關于Java控制臺輸入scanner的方法及實例代碼,有需要的朋友們可以參考下。
    2022-11-11
  • springboot整合mybatis-plus 實現(xiàn)分頁查詢功能

    springboot整合mybatis-plus 實現(xiàn)分頁查詢功能

    這篇文章主要介紹了springboot整合mybatis-plus 實現(xiàn)分頁查詢功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • 關于mybatis3中幾個@Provider的使用方式

    關于mybatis3中幾個@Provider的使用方式

    這篇文章主要介紹了關于mybatis3中幾個@Provider的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • SpringBoot整合FTP實現(xiàn)文件傳輸?shù)牟襟E

    SpringBoot整合FTP實現(xiàn)文件傳輸?shù)牟襟E

    這篇文章主要給大家介紹了SpringBoot整合FTP實現(xiàn)文件傳輸?shù)牟襟E,文中的流程步驟和代碼示例介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2023-11-11
  • JavaWeb實現(xiàn)RSA+AES混合加密

    JavaWeb實現(xiàn)RSA+AES混合加密

    RSA+AES的混合加密時,AES用于給傳輸?shù)臄?shù)據(jù)加密,然后通過RSA給AES的秘鑰加密,本文就來詳細的介紹一下如何實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • Spring Cloud重試機制與各組件的重試總結

    Spring Cloud重試機制與各組件的重試總結

    這篇文章主要給大家介紹了關于Spring Cloud中重試機制與各組件的重試的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2017-11-11
  • java并發(fā)學習-CountDownLatch實現(xiàn)原理全面講解

    java并發(fā)學習-CountDownLatch實現(xiàn)原理全面講解

    這篇文章主要介紹了java并發(fā)學習-CountDownLatch實現(xiàn)原理全面講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 詳解Java中ThreadLocal類型及簡單用法

    詳解Java中ThreadLocal類型及簡單用法

    ThreadLocal實例通常是希望將狀態(tài)與線程關聯(lián)起來的類中的私有靜態(tài)字段,下面通過例子給大家詳細介紹Java中ThreadLocal類型及簡單用法,感興趣的朋友跟隨小編一起看看吧
    2021-10-10
  • 一文教你如何通過三級緩存解決Spring循環(huán)依賴

    一文教你如何通過三級緩存解決Spring循環(huán)依賴

    這篇文章主要介紹了如何通過三級緩存解決?Spring?循環(huán)依賴,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考價值,需要的朋友可以參考下
    2023-07-07
  • 詳解DES加密算法及在Java程序中的使用示例

    詳解DES加密算法及在Java程序中的使用示例

    這篇文章主要介紹了詳解DES加密算法及在Java程序中的使用示例,文中還有一個用Java實現(xiàn)的DES三重加密的例子,需要的朋友可以參考下
    2016-04-04

最新評論

清水河县| 宜阳县| 伊川县| 张家界市| 开远市| 蒙自县| 仁布县| 读书| 南靖县| 贵阳市| 镇平县| 公主岭市| 邵东县| 泰来县| 汽车| 清流县| 湖口县| 东阳市| 温宿县| 社旗县| 云林县| 巧家县| 区。| 蓬溪县| 夏河县| 梁山县| 黎城县| 会理县| 邮箱| 尼木县| 浦城县| 包头市| 莱阳市| 平遥县| 上饶市| 东兰县| 昔阳县| 台安县| 蒙阴县| 江津市| 阿克陶县|