Spring循環依賴決策

循環依賴是什麼樣

  1. 我們自己手動創建多例對象,像這樣
public class A{
    private B beanB;
    public A(){
        beanB = new B();
    }
}
public class B{
    private A beanA;
    public B(){
        beanA = new A();
    }
}

這種情況,每次創建對象都是構造函數相互依賴,他們是實例化不同的對象,這種情況一直持續下去,在內存不斷構造新的對象出來,直到內存耗盡。

  1. 手動創建單例對象,像這樣
public class Test {
    public static void main(String[] args) {
        A a = A.getInstance();
        System.out.println("Test中實例化A:" + a);
    }
}
class A {
    private static final A a = new A();
    private B b;

    private A() {
        b = B.getInstance();
        System.out.println("A中實例化B:" + b);
    }
    public static A getInstance() {
        return a;
    }
}
class B {
    private static final B b = new B();
    private A a;

    private B() {
        a = A.getInstance();
        System.out.println("B中實例化A:" + a);
    }
    public static B getInstance() {
        return b;
    }
}

輸出結果:

B中實例化A:null
A中實例化B:com.plg.jietiao.B@6b2fad11
Test中實例化A:com.plg.jietiao.A@79698539

這種情況,實例化貌似順利完成了,但是如果在實例化過程中,使用到對象a,那就會有空指針的問題,畢竟我們實例化對象之後,就是爲了要使用它。

  1. 使用常用的spring框架創建單例對象,把對象的實例化過程交給spring框架來完成,像這樣
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}
<?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-4.0.xsd">
    
    <bean id="beanA" class="com.test.bean.A" scope="singleton">
        <constructor-arg ref="beanB"/>
    </bean>
    <bean id="beanB" class="com.test.bean.B" scope="singleton">
        <constructor-arg ref="beanA"/>
    </bean>
</beans>

在兩個對象的構造函數中直接去實例化另外一個單例對象,造成循環構造的情況,這種情況spring直接拋出BeanCurrentlyInCreationException異常,程序執行中止。

  1. 使用spring框架創建單例對象,通過屬性設置的方式循環依賴,像這樣
public class A{
    private B beanB;
    public void setBeanB(B beanB) {
        this.beanB = beanB;
    }
}
public class B{
    private A beanA;
    public void setBeanA(A beanA) {
        this.beanA = beanA;
    }
}
<?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-4.0.xsd">
    
    <bean id="beanA" class="com.baidu.shopping.A">
        <property name="beanB" ref="beanB"/>
    </bean>
    
    <bean id="beanB" class="com.baidu.shopping.B">
        <property name="beanA" ref="beanA"/>
    </bean>
</beans>

在實例化一個對象的過程中,對屬性賦值,依賴於另外一個對象的實例化,而非構造函數去依賴另外一個對象,即使這些對象都是單例。這種情況是可以順序完成對象的實例化過程。

通過以上列舉4種情況來看,只有第4種解決循環依賴的方式,可以順利完成。那麼spring在對象實例化的過程中,是如何解決對象之間的循環依賴問題的?

對象實例化關鍵步驟

首先要分析一下,spring在創建對象的整個過程中,有哪些關鍵的步驟
在這裏插入圖片描述
最主要步驟2、4、7、8這幾個步驟

  • 步驟2:調用類的構造方法,完成實例化對象
  • 步驟4:通過setter,設置對象屬性
  • 步驟7:調用對象的afterPropertiesSet()方法
  • 步驟8:調用spring xml中的init方法。

循環依賴主要發生在第一、第二步。也就是構造器循環依賴和屬性循環依賴。接下來我們具體看看spring是如何處理循環依賴。

構造器循環依賴

構造器循環依賴,這種方式構造對象,是沒辦法完成的,spring會直接拋出對象創建異常BeanCurrentlyInCreationException,spring是如何檢測這種構造器循環依賴的呢?

spring在DefaultSingletonBeanRegistry類中,定義了一個私有集合:

/** Names of beans that are currently in creation */
private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));

每次在實例化對象之前,都會先調用這個集合的add方法,看如下代碼:

	/**
	 * Callback before singleton creation.
	 * <p>The default implementation register the singleton as currently in creation.
	 * @param beanName the name of the singleton about to be created
	 * @see #isSingletonCurrentlyInCreation
	 */
	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

將正在實例化對象的類完成路徑,存放在這個集合中,這個集合相當於一個創建bean的池子,所有正在被創建的beanName都會緩存在這個集合中,注意是正在創建。

如果add方法中檢測到當前實例化對象的類完成路徑,已經存在了,就會返回false,程序拋出異常,啓動中止。

如果正在創建的beanName不存在於集合中,就會add成功,纔會執行實例化對象之後的邏輯,這個beforeSingletonCreation方法被調用是在上圖中的步驟1,方法postProcessBeforeInstantiation()調用之前。

beforeSingletonCreation方法正常檢測通過之後,就執行上圖中對象的創建過程,一直到步驟9:postProcessAfterInitialization()方法執行完成,

單例對象創建完成之後,開始執行afterSingletonCreation方法:

	/**
	 * Callback after singleton creation.
	 * <p>The default implementation marks the singleton as not in creation anymore.
	 * @param beanName the name of the singleton that has been created
	 * @see #isSingletonCurrentlyInCreation
	 */
	protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

此時就會從singletonsCurrentlyInCreation集合中刪除當前正在實例化的對象beanName,刪除成功之後,就執行spring其他的步驟,其他的步驟不是本文關注的重點,忽略。

設想實例化A的時候構造函數依賴B,A->B,此時A和B都會在singletonsCurrentlyInCreation集合中,在實例化B的時候構造函數依賴A,B->A,此時檢測到A已經在集合中存在,拋出異常,結束程序。所以無論是創建單例還是多例對象,只要是構造器依賴,spring都會拋出異常,區別在於單例是在啓動的過程中拋出異常,多例對象,程序啓動的時候不會拋出異常,等到真正從spring容器中獲取實例對象的時候,纔會拋出異常,因爲多例對象的實例化是延遲到程序使用對象的時候。

屬性循環依賴

也就是通過類的setter方法循環依賴。那爲什麼spring在構造器循環依賴的情況,實例化對象會失敗,通過屬性賦值的方式循環依賴,就可以完成實例化對象呢?

這種情況,spring引入了3個Map集合來解決這個問題,DefaultSingletonBeanRegistry類中,定義了3個私有集合,分別是:

	/** Cache of singleton objects: bean name --> bean instance */
	/** 一級緩存:用於存放完全實例化完成的 bean **/
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

	/** Cache of early singleton objects: bean name --> bean instance */
	/** 二級緩存:循環對象依賴列表,對象在創建之後,進行注入過程中,發現產生了循環依賴,那麼會將對象放入到這個隊列,並且從三級緩存singletonFactories中移除掉。 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
	
	/** Cache of singleton factories: bean name --> ObjectFactory */
	/** 三級級緩存:存放 bean 工廠對象,用於解決循環依賴 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

首先看看涉及到這幾個集合變量的關鍵代碼:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized(this.singletonObjects) {
        	//代碼編號1
        	//一級緩存singletonObjects不包含當前beanName,說明當前bean還沒有實例化完成
            if (!this.singletonObjects.containsKey(beanName)) {
            	//代碼編號2
            	//把當前正在實例化的bean,緩存在三級緩存singletonFactories中
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

首先的問題是這個方法addSingletonFactory,在對象實例化的什麼階段被調用的?看看上面對象被創建的幾個關鍵步驟圖,這是發生在步驟3:調用bean的構造函數之後,步驟4:調用populateBean方法之前,populateBean方法就是進行屬性值的賦值操作,這個方法最主要就是把當前已經調用構造函數的bean,緩存在三級緩存singletonFactories中,表示當前這個bean已經被構造出來了,只是還沒有進行bean屬性賦值和初始化的操作。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//代碼編碼1
		//從一級緩存singletonObjects中檢查當前正在創建的bean所依賴的bean,是否已經被實例化完成,如果已經實例化完成了,直接退出當前方法
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
            	//代碼編碼2
            	//從二級緩存earlySingletonObjects中檢查當前正在創建的bean所依賴的bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                	//代碼編碼3
                	//從三級緩存singletonFactories中檢查當前正在創建的bean所依賴的bean,是否已經構造完成
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //代碼編碼4
                        //當前正在創建的bean所依賴的bean,也正在實例化,剛剛構造出來,還沒有進行屬性賦值階段,將依賴的bean緩存在二級緩存earlySingletonObjects中,並將依賴的bean從三級緩存中移除
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject != NULL_OBJECT ? singletonObject : null;
    }

看看上面這段代碼

  • 第一個問題是要搞明白這個方法getSingleton,在對象創建的什麼階段調用的?getSingleton方法被調用是發生在populateBean()階段,也就是在給對象屬性賦值的階段。
  • 第二個問題這個getSingleton方法的參數beanName,表示的是當前正在實例化的bean,還是當前正在實例化的bean所依賴的bean:不是表示當前正在創建的bean,而是當前正在創建的bean所依賴的bean,比如以下代碼中,當前正在實例化beanB,如果beanB屬性值依賴了beanA,則此時這個beanName應該是beanA,
	<bean id="beanB" class="com.test.bean.B" scope="singleton">
        <property name="a" ref="beanA"/>
    </bean>
  • 第三個問題就是這段代碼在幹嘛,已經在代碼的註釋中註明了。beanName的檢測是從一級緩存singletonObjects開始,如果一級緩存不存在,就檢測二級緩存earlySingletonObjects,如果二級緩存也不存在,則檢測三級緩存singletonFactory,如果三級緩存中存在,就將對象直接從三級緩存移除,放到二級緩存中。那就會有下一個問題,什麼時候從二級緩存移動到一級緩存
  • 第四個問題bean對象什麼時候從二級緩存移動到一級緩存?看看以下代碼先,addSingleton的調用是發生在什麼階段呢?發生在步驟9之後,也就是調用postProcessAfterInitialization()方法之後,這個時候,當前正在實例化的bean接近尾聲,已經完成了構造、屬性賦值、初始化等操作。addSingleton這個方法要做的事情就是:把當前正在實例化的bean存放到一級緩存singletonObjects中,並從二級緩存和三級緩存中移除,這就表示當前這個bean已經完成實例化的過程。
protected void addSingleton(String beanName, Object singletonObject) {
        synchronized(this.singletonObjects) {
        	//代碼編碼1
        	//存放到一級緩存singletonObjects中
            this.singletonObjects.put(beanName, singletonObject != null ? singletonObject : NULL_OBJECT);
            //從三級緩存singletonFactories中移除
            this.singletonFactories.remove(beanName);
            //從二級緩存earlySingletonObjects中移除
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }

總結屬性循環依賴
檢測循環依賴的過程如下:

  • A創建過程中,A將自己放到三級緩裏面,賦值階段發現需要B,於是去實例化B
  • B實例化的時候,B先將自己放到三級緩裏面 ,賦值階段發現需要A,接着B先查一級緩存有沒有A,如果沒有,再查二級緩存,還是沒有,再查三級緩存找A,找到了。
    • 然後把三級緩存裏面的A放到二級緩存裏面,並刪除三級緩存裏面的A
    • B順利初始化完畢,B將自己從三級緩存中移除,並放到一級緩存裏面(此時B裏面的A依然是創建中狀態)
  • 然後回來接着創建A,此時B已經創建結束,直接從一級緩存裏面拿到B ,A完成創建,
  • 接着A將自己從二級緩存中移除,並將自己放到一級緩存裏面,這樣便解決了屬性賦值循環依賴的問題。

第二級緩存能不能取消

回顧上面的這個過程,發現二級緩存只是緩存了被循環依賴的bean,比如A依賴B,B依賴C,C依賴A,先構造對象A,再構造B,再構造C,整個過程中二級緩存中只緩存了A,那麼有個問題,二級緩存能不能取消掉,只用到一級緩存和三級緩存來解決循環依賴,我想了一下,貌似可以取消。

此處說到可以取消二級緩存,是因爲本文舉例都是普通的對象,不涉及到代理對象,如果有代理對象加入,看看會是什麼情況。回頭看看二級緩存和三級緩存存儲的對象的區別

	/** 二級緩存:循環對象依賴列表,對象在創建之後,進行注入過程中,發現產生了循環依賴,那麼會將對象放入到這個隊列,並且從三級緩存singletonFactories中移除掉。 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
	
	/** 三級級緩存:存放 bean 工廠對象,用於解決循環依賴 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  • 二級緩存earlySingletonObjects Map
    • key存儲的是beanName
    • value類型是Object,存儲的是實例化出來的對象,包括普通對象或者代理對象
  • 三級緩存singletonFactories Map
    • key存儲的是beanName
    • value類型ObjectFactory,存儲的是ObjectFactory接口的匿名類,他是一個對象工廠,只有一個方法getObject(),可以改變類的實例化結果,比如一個普通對象,通過這個工廠之後,返回一個代理對象

再一次看看查找對象的代碼

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                    	//代碼編碼1
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject != NULL_OBJECT ? singletonObject : null;
    }

具體看看代碼編碼1處,如果涉及到代理對象,singletonFactory.getObject()此處就會創建出來依賴對象的一個代理類出來(至於爲什麼不是本文分析的重點,忽略),依賴對象的代理類被存放到了二級緩存earlySingletonObjects中,key依然是beanName,value是依賴對象的代理類。

試想我們把二級緩存和三級緩存合成一個緩存,遇到代理類的情況,Map集合的key都是beanName,原始對象和代理對象形成相互覆蓋,不能區分出我是想要代理對象還是原始對象,三級緩存中存儲的對象是一個半成品,這個半成品轉移到二級緩存之後,就有兩種形態存在,一種是本來的對象,這種就不涉及代理,第二種就是代理對象,這種就涉及到代理,最後被移動到一級緩存中,也是代理對象。

參考

一文說透 Spring 循環依賴問題
spring是如何解決循環依賴的

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