Spring IoC 循環依賴的處理

前言

本系列所有基於 Spring 5.2.2.BUILD-SNAPSHOT 版本。由於 Spring 整個體系太過於龐大,因此只會進行關鍵部分的源碼解析。html

本篇文章主要介紹 Spring IoC 是怎麼解決循環依賴的問題的。java

正文

什麼是循環依賴

循環依賴就是循環引用,就是兩個或多個 bean 相互之間的持有對方,好比A引用B,B引用A,像下面僞代碼所示:git

public class A {
    private B b;
    
    // 省略get和set方法...
}
public class B {
    private A a;
    
    // 省略get和set方法...
}

Spring 如何解決循環依賴

Spring IoC 容器對循環依賴的處理有三種狀況:github

  1. 構造器循環依賴:此依賴 Spring 沒法處理,直接拋出 BeanCurrentlylnCreationException 異常。
  2. 單例做用域下的 setter 循環依賴:此依賴 Spring 經過三級緩存來解決。
  3. 非單例的循環依賴:此依賴 Spring 沒法處理,直接拋出 BeanCurrentlylnCreationException 異常。

構造器循環依賴

仍是假設上面的A和B類是構造器循環依賴,以下所示:spring

public class A {
    private B b;
    
    public A(B b) {
        this.b = b;
    }
    
    // 省略get和set方法...
}
public class B {
    private A a;
    
    public B(A a) {
        this.a = a;
    }
    
    // 省略get和set方法...
}

而後咱們在 XML 中配置了構造器自動注入,以下:緩存

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="constructor" />

    <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="constructor" />

</beans>

那麼咱們在獲取 A 時,首先會進入 doGetBean() 方法(該方法在Spring IoC bean 的加載中分析過),會進行到以下代碼塊:app

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    // 省略其它代碼...
    
    // 若是 bean 的做用域是單例
    if (mbd.isSingleton()) {
        // 建立和註冊單例 bean
        sharedInstance = getSingleton(beanName, () -> {
            try {
                // 建立 bean 實例
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
        // 獲取bean實例
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    
    // 省略其它代碼...
 
}

上面方法中的 getSingleton() 方法會判斷是不是第一次建立該 bean,若是是第一次會先去建立 bean,也就是調用 ObjectFacotygetObject() 方法,即調用 createBean() 方法建立 bean 前,會先將當前正要建立的 bean 記錄在緩存 singletonsCurrentlyInCreation 中。函數

在建立A時發現依賴 B,便先去建立 B;B在建立時發現依賴A,此時A由於是經過構造函數建立,因此沒建立完,便又去建立A,發現A存在於 singletonsCurrentlyInCreation,即正在建立中,便拋出 BeanCurrentlylnCreationException 異常。post

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    // 加鎖
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        // 一級緩存中不存在當前 bean,也就是當前 bean 第一次建立
        if (singletonObject == null) {
            // 若是當前正在銷燬 singletons,拋出異常
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
            }
            // 建立單例 bean 以前的回調
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // 獲取 bean 實例
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
				}
            // 省略異常處理...
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 建立單例 bean 以後的回調
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 將 singletonObject 放入一級緩存,並從二級和三級緩存中移除
                addSingleton(beanName, singletonObject);
            }
        }
        // 返回 bean 實例
        return singletonObject;
    }
}

// 單例 bean 建立前的回調方法,默認實現是將 beanName 加入到當前正在建立 bean 的緩存中,
// 這樣即可以對循環依賴進行檢測
protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

// 單例 bean 建立後的回調方法,默認實現是將 beanName 從當前正在建立 bean 的緩存中移除
protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 這邊bean已經初始化完成了,放入一級緩存
        this.singletonObjects.put(beanName, singletonObject);
        // 移除三級緩存
        this.singletonFactories.remove(beanName);
        // 移除二級緩存
        this.earlySingletonObjects.remove(beanName);
        // 將 beanName 添加到已註冊 bean 緩存中
        this.registeredSingletons.add(beanName);
    }
}

setter循環依賴

仍是假設上面的A和B類是 field 屬性依賴注入循環依賴,以下所示:ui

public class A {
    private B b;
    
    // 省略get和set方法...
}
public class B {
    private A a;
    
    // 省略get和set方法...
}

而後咱們在 XML 中配置了按照類型自動注入,以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="byType" />

    <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="byType" />

</beans>

Spring 在解決單例循環依賴時引入了三級緩存,以下所示:

// 一級緩存,存儲已經初始化完成的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二級緩存,存儲已經實例化完成的bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 三級緩存,存儲建立bean實例的ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

// 按前後順序記錄已經註冊的單例bean
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

首先在建立A時,會進入到 doCreateBean() 方法(前面的流程能夠查看Spring IoC bean 的建立一文),以下:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
    // 獲取bean的實例
    BeanWrapper instanceWrapper = null;
    if (instanceWrapper == null) {
        // 經過構造函數反射建立bean的實例,可是屬性並未賦值
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // 獲取bean的實例
    final Object bean = instanceWrapper.getWrappedInstance();
    
    // 省略其它代碼...

    // bean的做用域是單例 && 容許循環引用 && 當前bean正在建立中
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 若是容許bean提早曝光
    if (earlySingletonExposure) {
        // 將beanName和ObjectFactory造成的key-value對放入singletonFactories緩存中
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    
    // 省略其它代碼...
    
}

在調用 addSingletonFactory() 方法前A的實例已經建立出來了,只是還未進行屬性賦值和初始化階段,接下來將它放入了三級緩存中,以下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    // 加鎖
    synchronized (this.singletonObjects) {
        // 若是一級緩存中不包含當前bean
        if (!this.singletonObjects.containsKey(beanName)) {
            // 將ObjectFactory放入三級緩存
            this.singletonFactories.put(beanName, singletonFactory);
            // 從二級緩存中移除
            this.earlySingletonObjects.remove(beanName);
            // 將beanName加入到已經註冊過的單例bean緩存中
            this.registeredSingletons.add(beanName);
        }
    }
}

接下來A進行屬性賦值階段(會在後續文章中單獨分析這個階段),發現依賴B,便去獲取B,發現B尚未被建立,因此走建立流程;在B進入屬性賦值階段時發現依賴A,就去調用 getBean() 方法獲取A,此時會進入 getSingleton() 方法(該方法的調用流程在Spring IoC bean 的加載一文中分析過),以下:

public Object getSingleton(String beanName) {
    // allowEarlyReference設置爲true表示容許早期依賴
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先從一級緩存中,檢查單例緩存是否存在
    Object singletonObject = this.singletonObjects.get(beanName);
    // 若是爲空,而且當前bean正在建立中,鎖定全局變量進行處理
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 從二級緩存中獲取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 二級緩存爲空 && bean容許提早曝光
            if (singletonObject == null && allowEarlyReference) {
                // 從三級緩存中獲取bean對應的ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 調用預先設定的getObject(),獲取bean實例
                    singletonObject = singletonFactory.getObject();
                    // 放入到二級緩存中,並從三級緩存中刪除
                    // 這時bean已經實例化完但還未初始化完
                    // 在該bean未初始化完時若是有別的bean引用該bean,能夠直接從二級緩存中取出返回
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

嘗試一級緩存 singletonObjects (確定沒有,由於A還沒初始化徹底),嘗試二級緩存 earlySingletonObjects(也沒有),嘗試三級緩存 singletonFactories,因爲A經過 ObjectFactory 將本身提早曝光了,因此B可以經過 ObjectFactory.getObject() 拿到A對象(雖然A尚未初始化徹底,可是總比沒有好呀)。B拿到A後順利建立並初始化完成,調用上面分析過的 addSingleton() 方法將本身放入一級緩存中。此時返回A中,A也能順利拿到徹底初始化的B進行後續的階段,最後也將本身放入一級緩存中,並從二級和三級緩存中移除。

過程圖以下所示:

非單例循環依賴

對於非單例的 bean,Spring 容器沒法完成依賴注入,由於 Spring 容器不進行緩存,所以沒法提早暴露一個建立中的 bean

總結

本文主要介紹了 Spring 對三種循環依賴的處理,其實還有一種字段循環依賴,好比 @Autowired 註解標註的字段,但它和 setter 循環依賴的解決方法同樣,這裏就沒有多說。

最後,我模仿 Spring 寫了一個精簡版,代碼會持續更新。地址:https://github.com/leisurexi/tiny-spring

參考

相關文章
相關標籤/搜索