1. 程式人生 > >Spring原始碼解析-6、spring單例如何解決迴圈依賴

Spring原始碼解析-6、spring單例如何解決迴圈依賴

什麼叫迴圈依賴

迴圈依賴即兩個及以上的bean物件互相持有對方的引用,最終形成一個閉環。

spring如何處理正在建立的Bean

Spring容器會將每一個正在建立的Bean 識別符號放在一個“當前建立Bean池”中,Bean識別符號在建立過程中將一直保持
在這個池中,因此如果在建立Bean過程中發現自己已經在“當前建立Bean池”裡時將丟擲
BeanCurrentlyInCreationException異常表示迴圈依賴;而對於建立完畢的Bean將從“當前建立Bean池”中清除掉。

迴圈依賴的三種情況

構造器

public class BeanA {
    private BeanB b;

    public BeanA(BeanB b) {
        this.b = b;
    }
}

public class BeanB {
    private BeanC c;

    public BeanB(BeanC c) {
        this.c = c;
    }
}

public class BeanC {
    private BeanA a;

    public BeanC(BeanA a) {
        this.a = a;
    }
}

----------------------
 <bean id="a" class="com.raycloud.dmj.data.utils.BeanA">
        <constructor-arg index="0" ref="b"/>
    </bean>
    <bean id="b" class="com.raycloud.dmj.data.utils.BeanB">
        <constructor-arg index="0" ref="c"/>
    </bean>
    <bean id="c" class="com.raycloud.dmj.data.utils.BeanC">
        <constructor-arg index="0" ref="a"/>
    </bean>
----------------------
   ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

報錯:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?

Spring容器先建立單例BeanA,BeanA依賴BeanB,然後將A放在“當前建立Bean池”中,此時建立BeanB,BeanB依賴BeanC ,然後將B放在“當前建立Bean池”中,此時建立BeanC,StudentC又依賴BeanA, 但是,此時BeanA已經在池中,所以會報錯,,因為在池中的Bean都是未初始化完的,所以會依賴錯誤 ,(初始化完的Bean會從池中移除)

單例setter

public class BeanA {
    public BeanB b;

    public void setB(BeanB b) {
        this.b = b;
    }
}
public class BeanB {
    public BeanC c;

    public void setC(BeanC c) {
        this.c = c;
    }
}
public class BeanC {
    public BeanA a;

    public void setA(BeanA a) {
        this.a = a;
    }
}
----------------------
<bean id="a" class="com.raycloud.dmj.data.utils.BeanA">
        <property name="b" ref="b"/>
    </bean>
    <bean id="b" class="com.raycloud.dmj.data.utils.BeanB">
        <property name="c" ref="c"/>
    </bean>
    <bean id="c" class="com.raycloud.dmj.data.utils.BeanC">
        <property name="a" ref="a"/>
    </bean>
----------------------
 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        BeanA a = context.getBean("a", BeanA.class);
        BeanB b = context.getBean("b", BeanB.class);
        BeanC c = context.getBean("c", BeanC.class);
        System.out.println(String.format("a:%s,a.b:%s",a,a.b));
        System.out.println(String.format("b:%s,b.c:%s",b,b.c));
        System.out.println(String.format("c:%s,c.a:%s",c,c.a));

輸出:
a:[email protected],a.b:[email protected]
b:[email protected],b.c:[email protected]
c:[email protected],c.a:[email protected]
可以看到單例setter迴圈依賴沒有報錯,且迴圈的依賴都成功set。具體實現原理後面詳細看

原型setter

<bean id="a" class="com.raycloud.dmj.data.utils.BeanA"  scope="prototype">
        <property name="b" ref="b"/>
    </bean>
    <bean id="b" class="com.raycloud.dmj.data.utils.BeanB"  scope="prototype">
        <property name="c" ref="c"/>
    </bean>
    <bean id="c" class="com.raycloud.dmj.data.utils.BeanC"  scope="prototype">
        <property name="a" ref="a"/>
    </bean>

同樣報錯:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?

因此可以知道多例模式下也不能解決迴圈依賴。
為什麼?
對於“prototype”作用域Bean,Spring容器無法完成依賴注入,因為“prototype”作用域的Bean,Spring容
器不進行快取,因此無法提前暴露一個建立中的Bean。

單例模式如何解決迴圈依賴

在這裡插入圖片描述
如圖,set方法注入,spring先例項化Bean[通過無參構造器],在設定屬性,這樣就不會報錯了。
具體如何解決迴圈依賴
以我們的例項來說,spring會先例項化a,b,c,並放入一個map,然後設定屬性,a設定屬性b,只需要從mao中取出b即可,以此類推。

而事實上spring可不止這一個map,而是通過三級快取來解決單例Bean的迴圈依賴。

三級快取

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
 
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

singletonObjects:存放單例物件例項的快取
earlySingletonObjects:存在提前曝光的bean,也就是正在建立中的bean。
singletonFactories :建立單例物件的工廠

實現原理

實現原理的程式碼如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先從單例快取中取
		Object singletonObject = this.singletonObjects.get(beanName);
		//一級快取中沒有,並且判斷正在建立中【比如A的構造器依賴B,或者已經例項化正在setB  ,所以先去建立B,那麼A就是建立中】
		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;
	}

簡單來說:
在建立單例Bean的時候是這樣解決迴圈依賴的。
假設A B互相依賴。
先通過createBean建立A,會先走createInstance來例項化A,然後把A的單例工廠放到三級快取,例項化後需要設定屬性,發現需要B,但是B沒有初始化,因此通過createBean建立,同樣需要例項化,例項化以後發現依賴A,因此先去單例快取中找,因為A還在建立中,所以找不到,然後去二級快取找,依舊找不到,因此最後通過單例工廠建立獲取了A,B就建立好了,B建立好了,就set給A,此時A.B都例項化成功了。