Spring的迴圈依賴,學就完事了【附原始碼】

天喬巴夏丶發表於2020-09-10

啥是迴圈依賴?

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

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

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

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

Spring可以解決迴圈依賴的條件

一、出現迴圈依賴的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?

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

  • 均採用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的時候,應該沒啥問題,迴圈依賴的問題應該發生在第二步屬性注入的時候,而這時後這個例項的狀態,正好處於:已經例項化,還未初始化的中間狀態。這一點非常關鍵!!!!

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;
}

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

//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方法非常關鍵。
	//1、標註a正在建立中~
	//2、呼叫singletonObject = singletonFactory.getObject();(實際上呼叫的是createBean()方法)  因此這一步最為關鍵
	//3、標註此時例項已經建立完成
	//4、執行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方法:  1、如果有SmartInstantiationAwareBeanPostProcessor,呼叫他的getEarlyBeanReference方法,2、如果沒有,則不變還是,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在生命週期的最後一步完成代理而不是在例項化後就立馬完成代理。

總結

圖片來源:https://blog.csdn.net/f641385712/article/details/92801300

Spring通過三級快取解決了迴圈依賴:

  • singletonObjects:一級快取,一個單例bean【例項化+初始化】都完成之後,將會加入一級快取,也就是我們俗稱的單例池。
  • earlySingletonObjects:二級快取,用於存放【例項化完成,還沒初始化】的例項,提前暴露,用於解決迴圈依賴問題。
  • singletonFactories:三級快取,存放單例物件工廠ObjectFactory,與二級快取不同的是,它可以應對產生代理物件。

Spring不能夠解決先用構造器注入情況的迴圈依賴,原因在於Spring解決迴圈依賴的關鍵在於bean例項例項化完成,初始化之前的狀態,將其加入三級快取,提前暴露bean。


最後,迴圈依賴應當在編碼的時候就考慮去儘量避免,如果避免不了,那就儘量不要使用構造器注入,可以使用欄位注入。

有點暈了,本來想簡單地學習一下,沒想到一套接著一套,頭暈眼花,還是程式碼看的太少了,繼續努力。感覺有點亂,如果有說的不對的地方,還望評論區指點一二!!抱拳!!!

參考資料:

相關文章