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

啥是循環依賴?

下面這種情況比較常見,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通過多個緩存達到存儲不同狀態的對象:

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

如果只有一級緩存,並發情況下,可能取到實例化但未初始化的對象。

為什麼需要三級緩存,直接二級暴露引用不行么?

三級緩存使用的是工廠,而不是引用,原因在於://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在生命周期的最後一步完成代理而不是在實例化後就立馬完成代理。

總結

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

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

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

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

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

參考資料: