Spring的循環依賴,學就完事了【附源碼】

啥是循環依賴?

下面這種狀況比較常見,A中注入了屬性B,B中注入了A屬性。java

@Component
public class A {
    @Autowired
    private B b; //在A中注入B
}
@Component
public class B {
    @Autowired
    private A a; //在B中注入A
}

還有一種極限狀況,A中注入屬性A。spring

@Component
public class A {
    @Autowired
    private A a;
}

Spring能夠解決循環依賴的條件

1、出現循環依賴的Bean必須是單例,原型不行。緩存

第一點很好理解,也很好驗證,由於原型的Bean,每次獲取的時候都會建立一個新的,那麼問題來了,假設在初始化A的時候,須要注入原型的B,接着新建一個A,又新建B……無窮盡。若是真是這樣,那還得了。所以,原型狀況下Spring沒法解決循環依賴,會報錯:併發

aused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?app

2、不全是構造器注入的方式。ide

  • 均採用setter方法注入,能夠被解決。
  • 全是構造器注入,沒法被解決。
  • setter和構造器都存在,具體狀況具體分析,Spring會按照AB的順序選擇新建立哪一個。爲何先構造器不行,其實和Spring解決循環依賴的策略相關,後面會談到。
依賴狀況 依賴注入方式 循環依賴是否被解決
AB相互依賴(循環依賴) 均採用setter方法注入
AB相互依賴(循環依賴) 均採用構造器注入
AB相互依賴(循環依賴) A中注入B的方式爲setter方法,B中注入A的方式爲構造器
AB相互依賴(循環依賴) B中注入A的方式爲setter方法,A中注入B的方式爲構造器

Spring如何去解決循環依賴

SpringBean的建立流程

在討論Spring如何解決循環依賴以前,咱們須要清除SpringBean的建立流程,以前的那篇文章討論了容器的啓動銷燬與對象完整的生命週期,這裏將其中涉及循環依賴的主要部分再作一個說明:函數

  • createBeanInstance:實例化,其實也就是調用對象的構造方法或者工廠方法實例化對象
  • populateBean:填充屬性,這一步主要是對bean的依賴屬性進行注入(@Autowired)
  • initializeBean:回調執行initMethodInitializingBean等方法

能夠想到,對於單例的bean,在createBeanInstance的時候,應該沒啥問題,循環依賴的問題應該發生在第二步屬性注入的時候,而這時後這個實例的狀態,正好處於:已經實例化,還未初始化的中間狀態。這一點很是關鍵!!!!post

Spring維護的三級緩存

DefaultSingletonBeanRegistry類中,維護了三個註釋以Cache of開頭的Map,經過檢討能夠注意到,三級緩存與前兩級緩存不太同樣,Map中維護的值是ObjectFactory類型。學習

//單例緩存池 beanName - instance 一級緩存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//bean的早期引用, bean name to bean instance 二級緩存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

//單例工廠 beanName - ObjectFactory  三級緩存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • singletonObjects:一級緩存,一個單例bean【實例化+初始化】都完成以後,將會加入一級緩存,也就是咱們俗稱的單例池。
  • earlySingletonObjects:二級緩存,用於存放【實例化完成,還沒初始化】的實例,提早暴露,用於解決循環依賴問題。
  • singletonFactories:三級緩存,存放單例對象工廠ObjectFactory,與二級緩存不一樣的是,它能夠應對產生代理對象。
@FunctionalInterface //函數式接口
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}

還有幾個比較重要的集合:this

//bean被建立完成以後,註冊
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

//正在建立過程當中的bean待的地兒,bean在開始建立的時候放入,知道建立完成將其移除
private final Set<String> singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap<>(16));

getSingleton

AbstractBeanFactory.doGetBean中將會出現兩個重載的getSingleton方法:

protected <T> T doGetBean(...){

    Object sharedInstance = getSingleton(beanName);//
	// // typeCheckOnly 爲 false,將當前 beanName 放入一個 alreadyCreated 的 Set 集合中。表示已經建立過一次
	if (!typeCheckOnly) {
		markBeanAsCreated(beanName);
	}
	// 這個getSingleton方法很是關鍵。
	//一、標註a正在建立中~
	//二、調用singletonObject = singletonFactory.getObject();(實際上調用的是createBean()方法)  所以這一步最爲關鍵
	//三、標註此時實例已經建立完成
	//四、執行addSingleton()添加進一級緩存,
    //同時移除二級和三級緩存,還有註冊
	sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}

getSingleton重載一號

protected Object getSingleton(String beanName, boolean allowEarlyReference)

咱們的流程進行到AbstractBeanFactory#doGetBean的時候,會執行Object sharedInstance = getSingleton(beanName);,接着會執行getSingleton(beanName,true),一路跟進去,最終會進到DefaultSingletonBeanRegistry 的getSingleton方法,這個方法十分重要,咱們具體看一看:

@Nullable
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);
            //仍是獲取不到,而且allowEarlyReference爲true
            if (singletonObject == null && allowEarlyReference) {  
                //從三級緩存中獲取
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
                if (singletonFactory != null) {
                    //循環依賴第二次進入的時候,發現A 的三級緩存,因而能夠獲取到A 的實例,
                    singletonObject = singletonFactory.getObject();
                    //獲取到以後將其置入二級緩存
                    this.earlySingletonObjects.put(beanName, singletonObject); 
                    //原先的那個就從三級緩存中移除
                    this.singletonFactories.remove(beanName); 
                }
            }
        }
    }
    return singletonObject;
}
  1. 先嚐試從一級緩存中獲取,若是獲取到,表示這個對象已經【初始化+實例化】所有完成,固然,對於循環依賴的案例來講,這一步都是獲取不到的。
  2. 若是一級緩存中獲取不到,不要緊,看看這個bean是否是正在建立中【已經開始實例化,但尚未初始化徹底】,若是是這個狀況,就嘗試從二級緩存中獲取。
  3. 若是都獲取不到,且allowEarlyReference爲true的時候,從三級緩存中取,三級緩存中存放的是ObjectFactory。

getSingleton重載二號

另一個Singleton重載的方法:public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
				//將beanName放入到singletonsCurrentlyInCreation這個集合中,標誌着這個單例Bean正在建立
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                // 傳入的lambda在這裏會被執行,調用createBean方法建立一個Bean後返回
                singletonObject = singletonFactory.getObject(); 
                newSingleton = true;
                singletonObject = this.singletonObjects.get(beanName);

            }
            // 建立完成後將對應的beanName從singletonsCurrentlyInCreation移除
            afterSingletonCreation(beanName);
    	}
    	if (newSingleton) {
            //加入一級緩存
        	addSingleton(beanName, singletonObject); 
    	}
	}
	return singletonObject;
}

addSingleton

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
            //加入一級緩存
			this.singletonObjects.put(beanName, singletonObject);
            //三級緩存移除
			this.singletonFactories.remove(beanName);
            //二級緩存移除
			this.earlySingletonObjects.remove(beanName);
            //註冊一下
			this.registeredSingletons.add(beanName);
		}
	}

addSingletonFactory

AbstractAutowireCapableBeanFactory#doCreateBean

在對象實例化完成,初始化以前進行:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {

    // Instantiate the bean. 實例化
    BeanWrapper instanceWrapper = null;
    // 調用構造器或工廠方法 實例化
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    ////咱們一般說的bean實例,bean的原始對象,並無進行初始化的對象 A{ b:null}
    Object bean = instanceWrapper.getWrappedInstance();
    
    //表示是否提早暴露原始對象的引用,對於單例的bean,通常來講爲true, 能夠經過allowCircularReferences關閉循環引用解決循環依賴問題
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName)); 
    //是否容許單例提早暴露
    if (earlySingletonExposure) {
        //調用這個方法,將一個ObjectFactory放進三級緩存,二級緩存會對應刪除
        //getEarlyBeanReference方法:  一、若是有SmartInstantiationAwareBeanPostProcessor,調用他的getEarlyBeanReference方法,二、若是沒有,則不變仍是,exposedObject
        //這裏也是AOP的實現之處,AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor

        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//在bean實例化後,屬性注入以前,Spring將bean包裝成一個工廠添加進三級緩存中
    }
	//此時bean已經實例化完成, 開始準備初始化
    // bean爲原始對象
    Object exposedObject = bean;
    try {
        //負責屬性的裝配(如依賴注入),遇到循環依賴的狀況,會在內部getBean("b")->getSingleton(b)
        populateBean(beanName, mbd, instanceWrapper);
        //處理bean初始化完成後的各類回調這裏有可能返回一個代理對象
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
	//若是bean容許被早期暴露,進入代碼
    if (earlySingletonExposure) { 
        //第二參數爲false表示不會從三級緩存中在檢查,最多從二級緩存中找,其實二級緩存就夠了,其實以前getSingleton的時候,已經觸發了A 的ObjectFactory.getObject(),A實例已經放入二級緩存中
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            //若是沒有代理,進入這個分支
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference; /
            }
            
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        //若是一級緩存中還沒有存在
        if (!this.singletonObjects.containsKey(beanName)) { 
            //添加到三級緩存中
            this.singletonFactories.put(beanName, singletonFactory);
            //從二級緩存中移除
            this.earlySingletonObjects.remove(beanName);
            //註冊一下
            this.registeredSingletons.add(beanName); 
        }
    }
}

getEarlyBeanReference

前面談到了這個方法,尚未細說:

//是否容許單例提早暴露
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

它實際上就是調用了後置處理器的getEarlyBeanReference,而真正實現了這個方法的後置處理器只有AbstractAutoProxyCreator,與Aop相關,也就是說,在不考慮Aop的狀況下,這個方法壓根就和沒調用似的。這裏咱們也能更加明確,三級緩存出現很大程度上也是爲了更好處理代理對象。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            //調用後值處理器的getEarlyBeanReference  
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

咱們能夠跟進去看一看:

//AbstractAutoProxyCreator
	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
        //若是須要的話,返回一個代理對象
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

那麼若是考慮可能會存在代理對象出現,這時三級緩存中存在的就是這個代理對象,而且以後經過getSingleton從三級緩存中取出,放入二級緩存中的也是這個對象。

解決循環依賴的流程

本質其實就是 讓A注入B,B注入A ,B先注入的是一個還沒初始化就提早用的A 的引用。【這裏不考慮AOP】

以開頭的A,B爲例,假設他們都使用屬性字段注入:

  1. A首先getBean,試圖獲取容器中單例A,第一次容器中還不存在,因而就須要開始建立A。

  2. 一頓操做,落點:A此時已經被實例化完成,可是尚未初始化,緊接着將A與一個ObjectFactory存入三級緩存 。若是A被AOP代理,經過這個工廠獲取到的就是A代理後的對象,若是沒有代理,工廠最後獲取到的就是A 的實例化對象。

  3. 初始化A,意爲A的屬性賦值,這時發現B須要注入,因而getBean,來一遍相同的步驟。

  4. 一頓操做,落點:B此時已經被實例化完成,可是尚未初始化,緊接着將B與一個ObjectFactory存入三級緩存 。

  5. 初始化B,發現須要注入A,因而getBean("a"),此時它在三級緩存中找到了A與ObjectFactory<?> singletonFactory,經過singletonFactory.getObject();獲得A的引用。並將其存入二級緩存,且從三級緩存移除 。

  6. B注入從對象工廠得到的A的引用,此時B已經初始化完成【表明已經注入A成功,實際上是擁有了A的引用】,將B加入到一級緩存,並將B在二級緩存、三級緩存中的玩意清除,返回。

  7. 剛剛是A初始化到一半切出來開始實例化B的,那麼接下來也應該返回到A的初始化流程中去。

  8. 顯然B都已經初始化完畢了,A固然也順利地初始化成功了,一樣,也將A加入一級緩存中,並將A在二級緩存、三級緩存中清除。

  9. 至此,Spring解決循環依賴結束,A與B都已實例化+初始化完成,並存入一級緩存,且二級緩存、三級緩存中已經沒有了A和B。

固然了,這個過程實際上是在實例化A的時候,把B一併實例化了,因而在遍歷BeanNames實例化B的時候,就不須要進行這麼複雜的操做了,由於一級緩存中已經存在B了。

爲何先用構造器注入不能解決循環依賴?

緣由在於,Spring解決循環依賴實際上是在Bean已經實例化但未初始化這個中間狀態的時候進行處理的,所以bean的實例化與初始化兩個操做必須分開,纔有機會存入三級緩存,提早暴露原始對象。

可是若是使用若是A先使用構造器,在注入的時候,他會去找B,B再注入A,可此時A並無暴露,也就失敗了。

但若是A先用setter注入,A會先暴露,再注入B,B再注入A的時候,就能夠經過三級緩存拿到A了。

僅用一級緩存能夠解決循環依賴麼?

顯然不能,Spring經過多個緩存達到存儲不一樣狀態的對象:

  • 實例化和初始化都已經完成。
  • 已經實例化,但還沒初始化。

若是隻有一級緩存,併發狀況下,可能取到實例化但未初始化的對象。

爲何須要三級緩存,直接二級暴露引用不行麼?

三級緩存使用的是工廠,而不是引用,緣由在於:https://mp.weixin.qq.com/s/kS0K5P4FdF3v-fiIjGIvvQ

延遲隊實例化階段生成的對象的代理,只有真正發生循環依賴的時候,纔去提早生成代理對象,不然只會建立一個工廠並將其放入到三級緩存中,可是不會去經過這個工廠真正建立對象。

答:這個工廠的目的在於延遲對實例化階段生成的對象的代理,只有真正發生循環依賴的時候,纔去提早生成代理對象,不然只會建立一個工廠並將其放入到三級緩存中,可是不會去經過這個工廠去真正建立對象

咱們思考一種簡單的狀況,就以單首創建A爲例,假設AB之間如今沒有依賴關係,可是A被代理了,這個時候當A完成實例化後仍是會進入下面這段代碼:

// A是單例的,mbd.isSingleton()條件知足
// allowCircularReferences:這個變量表明是否容許循環依賴,默認是開啓的,條件也知足
// isSingletonCurrentlyInCreation:正在在建立A,也知足
// 因此earlySingletonExposure=true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
// 仍是會進入到這段代碼中
if (earlySingletonExposure) {
 // 仍是會經過三級緩存提早暴露一個工廠對象
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

看到了吧,即便沒有循環依賴,也會將其添加到三級緩存中,並且是不得不添加到三級緩存中,由於到目前爲止Spring也不能肯定這個Bean有沒有跟別的Bean出現循環依賴。

假設咱們在這裏直接使用二級緩存的話,那麼意味着全部的Bean在這一步都要完成AOP代理。這樣作有必要嗎?

不只沒有必要,並且違背了Spring在結合AOP跟Bean的生命週期的設計!Spring結合AOP跟Bean的生命週期自己就是經過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來完成的,在這個後置處理的postProcessAfterInitialization方法中對初始化後的Bean完成AOP代理。若是出現了循環依賴,那沒有辦法,只有給Bean先建立代理,可是沒有出現循環依賴的狀況下,設計之初就是讓Bean在生命週期的最後一步完成代理而不是在實例化後就立馬完成代理。

總結

圖片來源:http://www.javashuo.com/article/p-smkruniq-nt.html

Spring經過三級緩存解決了循環依賴:

  • singletonObjects:一級緩存,一個單例bean【實例化+初始化】都完成以後,將會加入一級緩存,也就是咱們俗稱的單例池。
  • earlySingletonObjects:二級緩存,用於存放【實例化完成,還沒初始化】的實例,提早暴露,用於解決循環依賴問題。
  • singletonFactories:三級緩存,存放單例對象工廠ObjectFactory,與二級緩存不一樣的是,它能夠應對產生代理對象。

Spring不可以解決先用構造器注入狀況的循環依賴,緣由在於Spring解決循環依賴的關鍵在於bean實例實例化完成,初始化以前的狀態,將其加入三級緩存,提早暴露bean。


最後,循環依賴應當在編碼的時候就考慮去儘可能避免,若是避免不了,那就儘可能不要使用構造器注入,可使用字段注入。

有點暈了,原本想簡單地學習一下,沒想到一套接着一套,頭暈眼花,仍是代碼看的太少了,繼續努力。感受有點亂,若是有說的不對的地方,還望評論區指點一二!!抱拳!!!

參考資料:

相關文章
相關標籤/搜索