spring如何解決循環依賴問題

一、簡介:

       循環依賴就是循環引用,就是兩個或多個bean互相之間持有對方。比如beanA引用beanBbeanB引用beanA,當我們實例化beanA的時候發現beanB作爲beanA的成員對象出現了,那麼我們就可能在實例化beanA的中間需要先實例化beanB,然後完成beanB的實例化之後,才能完成beanA的實例化;可惜的是beanB中也引用了beanA,在實例化beanB過程中又需要實例化beanA,而beanA正在進行實例化,但完成beanA的實例化的條件是beanB實例化完成,完成beanB實例化的條件是完成beanA的實例化,於是他們最終反映爲一個環狀依賴,難以完成實例化。

二、spring中循環依賴的三種情況

1、構造器注入形成的循環依賴

       beanB需要在beanA的構造函數中完成初始化,beanA也需要在beanB的構造函數中完成初始化,這種情況的結果就是兩個bean都不能完成初始化,循環依賴難以解決

2、setter注入構成的循環依賴

       beanA需要在beanBsetter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring設計的機制主要就是解決這種循環依賴,也是今天要討論的重點。

3、prototype作用域bean的循環依賴

       這種循環依賴同樣無法解決,因爲spring不會緩存prototype作用域的bean,而spring中循環依賴的解決方式正是通過緩存來實現的。

三、spring對應循環依賴的解決

       spring循環依賴的理論依據其實是Java基於引用傳遞,當我們獲取到對象的引用時,對象的field是可以延後設置的。spring單例對象的初始化其實可以分爲三步:實例化、填充屬性、初始化

       實例化(createBeanInstance):就是調用對應的構造方法構造對象,此時只是調用了構造方法,並沒有進行屬性填充。

       填充屬性(populateBean):填充屬性。

       初始化(initializeBean):調用spring xml中指定的init方法,或者AfterPropertiesSet方法。

四、spring中的三級緩存

       對於單例對象來說,在spring的整個容器的生命週期內,有且只存在一個對象,很容易想到這個對象應該存在Cache中,spring大量運用了Cache的手段,在循環依賴問題的解決過程中甚至使用了”三級緩存“

// 一級緩存
private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);

// 三級緩存
private final Map<String,ObjectFactory<?>> singletonFactories = new HashMap<>();

// 二級緩存
private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);

一級緩存:singletonObjects,存放完全實例化屬性賦值完成的單例對象的cache,直接可以使用。

二級緩存:earlySingletonObjects,存放提前曝光的單例對象的cache,尚未進行屬性封裝的Bean

三級緩存:singletonFactories,存放單例對象工廠的cache

五、spring如何解決循環依賴

       spring在創建bean的工程中,首先會嘗試從緩存中獲取,這個緩存指的就是上面的singletonObjects(一級緩存),主要調用的方法如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = 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);}

       isSingletonCurrentlyInCreation():判斷對應的單例對象是否在創建中,(例如BeanA在填充屬性的過程中依賴了BeanB對象,得先去創建BeanB對象,此時BeanA處於創建中)。

      allowEarlyReference():是否允許從singletonFactories(三級緩存)中通過getObject拿到對象。

分析下getSingleton()的整個過程:

步驟一:

       spring首先從singletonObjects(一級緩存)中嘗試獲取。

步驟二:

       如果在一級緩存中獲取不到並且對象處於創建中狀態,則嘗試從earlySingletonObjects(二級緩存)中獲取。

步驟三:

       如果還是獲取不到並且允許從singletonFactories(三級緩存)通過getObject()獲取,則通過singletonFactory.getObject()(三級緩存)獲取。如果獲取到了則移除對應的singletonFactory,將singletonObject放入到earlySingletonObjects(二級緩存),其實就是將三級緩存提升到二級緩存中

this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

       spring解決循環依賴的訣竅就在於singletonFactories(三級緩存)這個cache,這個cache中存的是類型爲ObjectFactory,其定義如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

       在bean創建過程中,有兩處比較重要的匿名內部類實現了該接口,上面已經提到了,spring利用他來創建bean

new ObjectFactory<Object>() {
    @Override   
    public Object getObject() throws BeansException {
        try {
            return createBean(beanName, mbd, args);
        }catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }   
    }
}

       另一處就是:

addSingletonFactory(beanName, new ObjectFactory<Object>() {
    @Override  
    public Object getObject() throws BeansException {
         return getEarlyBeanReference(beanName, mbd, bean);
    }
});

       此處就是解決循環依賴的關鍵,這段代碼發生在createBeanInstance(實例化)之後,也就是說單例對象此時已經被創建出來的。這個對象已經被生產出來了,雖然還不完美還沒有進行填充屬性和初始化),但是已經能被人認出來了(根據對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用。

六、總結:

       BeanA的某個field或者setter依賴了BeanB的實例對象,同時BeanB的某個field或者setter依賴了BeanA的實例對象,出現這種循環依賴的情況。

       BeanA首先完成了初始化的第一步(實例化),並且將自己放到singletonFactories(三級緩存)中,此時進行初始化的第二步(屬性填充),發現自己依賴對象BeanB,此時就嘗試去get(BeanB),發現BeanB還沒有被create,所以走create流程。

       BeanB初始化第一步(實例化)的時候發現自己依賴了對象BeanA,於是嘗試get(BeanA),嘗試從一級緩存singletonObjects(肯定沒有,因爲BeanA還沒初始化完全),嘗試從二級緩存earlySingletonObjects(也沒有),嘗試從三級緩存singletonFactories中獲取,由於BeanA通過ObjectFactory將自己提前曝光了,所以BeanB能夠通過ObjectFactory.getObject()拿到BeanA對象(雖然BeanA還沒有初始化完全,但是可以被發現),

       BeanB拿到BeanA對象後順利完成了初始化階段1、2、3(實例化、填充屬性、初始化),完全初始化之後將自己放入到一級緩存singletonObjects中。並且將BeanA放到二級緩存中,移除三級緩存中的BeanA

       此時返回BeanA中,BeanA此時能拿到BeanB的對象順利完成自己的初始化階段2、3(填充屬性和初始化),最終BeanA也完成了初始化,將BeanA也添加到了一級緩存singletonObjects中。

       知道了這個原理時候,肯定就知道爲啥Spring不能解決“BeanA的構造方法中依賴了BeanB的實例對象,同時BeanB的構造方法中依賴了BeanA的實例對象”這類問題了!因爲BeanB連初始化的第一步(實例化)都完成不了,無法進行下一步的操作。

參考博客:https://blog.csdn.net/quliuwuyiz/article/details/79416184?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159383602919725211902632%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=159383602919725211902632&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-79416184.first_rank_ecpm_v3_pc_rank_v4&utm_term=spring%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章