Spring面試題之循環依賴的理解


最近面試的時候發現很多人會問Spring是如何解決循環依賴的,雖然知道是通過三級緩存去解決的,但是也僅僅只是知其然,不知其所以然,抱着學習的心態還是好好捋一捋:

  • 三級緩存是如何解決循環依賴的?
  • 為什么是三級緩存?二級緩存行不行?
  • 有什么好的方式可以避免構建IOC的時候產生循環依賴?

循環依賴的場景

這個場景其實分為很多種:
簡單一點場景: A -> B -> A
復雜一點的場景:

  • A 依賴 B,C
  • B依賴A
  • C依賴A

在我們業務邏輯越來越復雜的時候,難免因為層級過深導致這種場景出現,但是在沒有運行的時候發現不了。

另外Spring是能夠解決set屬性賦值的循環依賴,但是構造器注入的是會有問題的,構造器在實例化的時候會出現死結,而set可以預先實例化后賦值所以好解決。

三級依賴是如何解決的?

首先我們要了解三級緩存的用處:

  • 一級緩存 singletonObjects : 用於保存實例化、注入、初始化完成的bean實例。

這里就是生命周期已經加載完成了的對象

  • 二級緩存 earlySingletonObjects : 用於保存實例化完成的bean實例.

其實也就是new完了的對象,但是沒有進行set(依賴注入)、以及初始化的對象,就是簡單的實例化對象。

  • 三級緩存 singletonFactories : 用於保存bean創建ObjectFactory工廠,方便后續可以創建代理對象。

這里很重要,這個工廠里面會包含bean的創建,可能是普通對象,可能是代理過后的對象。可以理解為先把new完之后的實例引用先獲取到。

循環依賴場景: A -> B -> A

了解大概流程,先不糾結於細節。
在這里插入圖片描述
緩存獲取順序的代碼:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton

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) {
            	// 從工廠里面獲取對應的值,可能是普通實例,可能是代理對象
                singletonObject = singletonFactory.getObject();
                // 放入二級緩存
                this.earlySingletonObjects.put(beanName, singletonObject);
                // 刪除三級緩存
                this.singletonFactories.remove(beanName);
            }
        }
    }
  }
	return singletonObject;
}

這里有個很重要的點:就是當B要獲取A的時候,從三級緩存里面查找,這時候已經能夠找到了,就會從ObjectFactory工廠中返回一個對象,這個對象可能是普通實例也可能是代理對象。 這個時候會加入到二級緩存中,下一次查找就能從一級,然后到二級直接找到對象了,不會在走到三級封裝成ObjectFactory對象了(每次從工廠里面拿可能會不是同一個實例)。

也就是說,發生循環的時候,會從工廠中將對象提前實例化出來,然后這個引用會被會注入到發生循環依賴的Bean作為屬性填充。

另外下面的代碼是創建bean中,會預先將bean封裝成ObjectFactory對象的代碼

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
// .. 省略
// 加入三級緩存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Object exposedObject = bean;

try {
	// 然后開始對該bean進行屬性賦值
	populateBean(beanName, mbd, instanceWrapper);
	// 執行init方法.初始化bean
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
}

//★★★★★★★★★★ 這里其實非常重要,待會再講。★★★★★★★★
if (earlySingletonExposure) {
	// 從二級緩存中獲取該bean
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		// 如果bean沒有被代理過,那么該條件是成立的.
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			String[] dependentBeans = getDependentBeans(beanName);
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			if (!actualDependentBeans.isEmpty()) {
				throw new BeanCurrentlyInCreationException(...);
			}
		}
	}
}

// Register bean as disposable.
try {
	registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
	throw new BeanCreationException(
			mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
// .. 省略
}
// 封裝之后,加入三級緩存.
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
			// 先加入三級緩存
			this.singletonFactories.put(beanName, singletonFactory);
			// 刪除二級緩存
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
// 這里是ObjectFactory對象構建並獲取的邏輯
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		// 這里會提前執行后置處理器,有可能會返回的是一個代理對象
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			// 通過這個SmartInstantiationAwareBeanPostProcessor類型的執行器,來獲取提前暴露對象的邏輯
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

// 當實例創建完成之后,會加入到單例工廠,從二級緩存升級到一級緩存中
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);
	}
}

這個時候B初始化好了直接加入到一級緩存中,而A在三級緩存被查找到后,放入二級緩存中,下一次查找會直接從二級緩存升級到一級緩存。流程結束。

我估計你現在還是會有點疑問:
ObjectFactory中獲取的對象確實是提前暴露實例化的對象,但是它是咋進行屬性填充的?

還是看下圖吧:

引用邏輯
我不知道注釋的代碼你能否理解...

總的來說就是引用邏輯,先將引用傳遞到工廠中,讓工廠構建早期bean的時候是基於這個實例的引用去做的,這個時候依賴注入給其他Bean的時候也是基於該引用,所以等到該bean初始化完成,其他被其依賴注入的bean的引用就是初始化完成的。

為什么要三級緩存? 二級緩存行不行?

從上面的流程上來看二級緩存只是為了將工廠得到的實例對象預先存儲在二級緩存中,作用也不是特別明顯。

但是首先要思考一個問題:假設干掉二級緩存,三級變成兩級。

假設循環依賴的場景是: A->B->C->B->A

在這里插入圖片描述

TestService1注入到TestService3又需要從第三級緩存中獲取實例,而第三級緩存里保存的並非真正的實例對象,而是ObjectFactory對象。
說白了,兩次從三級緩存中獲取都是ObjectFactory對象,而通過它創建的實例對象每次可能都不一樣的,比如代理對象,每次獲取的都是一個新的代理對象。

為了解決這個問題,spring引入的第二級緩存。上面圖1其實TestService1對象的實例已經被添加到第二級緩存中了,而在TestService1注入到TestService3時,只用從第二級緩存中獲取該對象即可。

所以二級緩存還是有必要的。區分工廠獲取的對象和具體實例的引用對象。

在這里插入圖片描述

還有個問題,第三級緩存中為什么要添加ObjectFactory對象,直接保存實例對象不行嗎?

答:不行,因為假如你想對添加到三級緩存中的實例對象進行增強,直接用實例對象是行不通的。

直接反射實例化的話,沒辦法經過SpringBean的后置處理器參考getEarlyBeanReference方法生成增強對象。

有什么好的方式避免循環依賴嗎?

比如我明顯知道A 依賴 B 了,這個時候B也需要A。

我們可以采用懶加載的方式以及容器先加載完的方式再獲取.

  1. @Layz注解,延遲加載
  2. B實現ApplicationContextAware的接口獲取到上下文,然后從上下文中獲取A,總的來說也是懶加載的思路

好了,以上僅僅是本人在遇到這個問題的一些延伸及參考加思考所寫,感謝你這么忙還能觀看我的文章。

如果有問題歡迎留言交流,我會及時回復,希望共同進步。

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM