spring如何解決迴圈依賴

2020-09-30 16:01:01

概述

迴圈依賴就是依賴關係形成環,比如最簡單的迴圈依賴:A物件依賴B,B物件依賴A

屬性注入與迴圈依賴

  1. 如果是構造器注入,如果迴圈依賴物件沒法構建,因為還未範例化
  2. 如果是屬性注入但是作用域是prototype,spring不會快取其物件範例,也不能處理迴圈依賴的情況
  3. 如果是屬性注入singleton的,其bean的範例化過程與屬性注入過程是分開的,並且spring提供了三個map(就是大家說三級快取)來實現。

spring屬性注入處理迴圈依賴的方式

通過以下xml方式設定一個迴圈依賴的範例:

<bean id="person1" class="com.example.leetcode.spring.bean.Person">
    <property name="parent" ref="person2"></property>
    <property name="name" value="tom"></property>
</bean>

<bean id="person2" class="com.example.leetcode.spring.bean.Person">
    <property name="parent" ref="person1"></property>
    <property name="name" value="jack"></property>
</bean>

spring迴圈依賴處理幾個關鍵位置:

  1. 獲取bean物件
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // 這裡會檢查單例bean是否已經在登入檔,並返回。
    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        if (logger.isTraceEnabled()) {
            if (isSingletonCurrentlyInCreation(beanName)) {
                logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                        "' that is not fully initialized yet - a consequence of a circular reference");
            }
            else {
                logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    ...
}
複製程式碼
  1. DefaultSingletonBeanRegistry(單例物件登入檔)的幾個關鍵屬性。
    // 用來儲存已經建立好的單例物件
    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 用來儲存單例beanname到ObjectFactory的對映
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    // 用來提前儲存還未初始化好的單例物件
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  1. DefaultSingletonBeanRegistry.getSingleton()的實現.
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;
}
  1. AbstractAutowireCapableBeanFactory.doCreateBean建立物件與注入屬性
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
    ...
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    ...
    // 檢查是否提前將單例bean存入快取
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 這裡將beanname與工廠對映放入快取登入檔中(也就是上面的singletonFactories)
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    
    ...
    // 注入依賴屬性
    populateBean(beanName, mbd, instanceWrapper);
    ...
                
}

假設我們從beanfactory獲取person1物件, 迴圈依賴處理流程如下:

  1. 通過AbstractBeanFactory.doGetBean("persion1")獲取物件
  2. 因為一開始通過DefaultSingletonBeanRegistry.getSingleton()什麼都沒有,進入AbstractAutowireCapableBeanFactory.doCreateBean()進行建立
  3. AutowireCapableBeanFactory.doCreateBean()裡面執行完建立邏輯,因為是singleton將beanname與工廠的對映加入到addSingletonFactory()到快取
  4. 開始處理person1物件的屬性依賴populateBean()
  5. 當發現person1的parent屬性是一個參照時,通過beanfactory.getBean("person2")獲取依賴物件(org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference)
  6. 此時進入person2的建立流程, person2也沒有快取,開始範例化並加入到addSingletonFactory()到快取
  7. person2在通過populateBean()注入屬性依賴發現依賴person1, 此時通過beanfactory.getBean("person1")獲取依賴物件
  8. 此時AbstractBeanFactory.doGetBean("persion1")獲取物件執行到getSingleton("person1")進行以下判斷:
    • 從singletonObjects.get(beanName)獲取到null
    • 進入if條件,對singletonObjects同步
    • 從earlySingletonObjects.get(beanName);獲取也為null
    • 進入內層if,通過singletonFactories.get(beanName);獲取到最開始bean範例化之後的beanname與工廠快取資訊
    • 獲取到僅範例化完成的bean,並earlySingletonObjects.put(beanName, singletonObject);
    • 然後刪除singletonFactories.remove(beanName);
  9. 此時從getSingleton("person1")返回了一個僅範例化尚未注入的bean參照
  10. person2在第7步獲取到person1僅範例化未注入的物件參照。
  11. person2完成屬性注入並返回。
  12. person2被addSingleton(beanName, singletonObject);中singletonObjects.put(beanName, singletonObject)快取,並刪除singletonFactories.remove(beanName);earlySingletonObjects.remove(beanName);
  13. person1在5步獲取到person2的物件並完成屬性注入
  14. person1物件返回(因為一開始person2獲取的是person1的參照,此時person1完成注入是能看到注入後的物件)
  15. person1被addSingleton(beanName, singletonObject);中singletonObjects.put(beanName, singletonObject)快取,並刪除singletonFactories.remove(beanName);earlySingletonObjects.remove(beanName);
  16. 返回最終的person1物件

關於三個map(三級快取)

在出現迴圈依賴時,三個map之間的流程如下:

  1. 先從singletonFactories獲取工廠,並通過getObject獲取物件並移除快取,將物件快取到earlySingletonObjects
  2. 通過earlySingletonObjects獲取提前曝光的物件
  3. 物件建立並初始化完成之後,物件資訊保留在singletonObjects並移除過earlySingletonObjects中的快取

earlySingletonObjects二級快取是雞肋嗎?

earlySingletonObjects快取的目的是,通過三級快取在獲取物件會執行一些列的後置處理器,通過earlySingletonObjects來快取提升效能。