spring-ioc中循環依賴的問題,也算是高頻的面試問題了,今天跟大家一起來總結一下spring-ioc中是如何解決循環依賴的,相信大家是可以從這篇文章中徹底理解spring容器如何幫我們解決循環依賴,爲了更好的理解spring-ioc如何解決循環依賴,大家可以先簡單的瞭解spring-ioc中bean實例化的整個時序圖。
一、spring-ioc解決循環依賴的位置
紅色的標註框的地方,表示解決循環依賴的重點邏輯,後面會跟大家一起詳細閱讀,這裏大家可以先有個印象
二、spring-ioc解決循環依賴的動態示意圖
首先spring-ioc是無法解決構造函數中循環依賴的問題,這個後面會一起解釋。咱們先用一個簡單的例子和示意圖來描述spring-ioc解決循環依賴的思想,最後再去閱讀源碼,我相信大家能更容易理解
@Service
public class ACircleService {
@Autowired
private BCircleService bCircleService;
}
@Service
public class BCircleService {
@Autowired
private ACircleService aCircleService;
}
相信大家經常這麼使用,此時ACircleService(後面簡稱A)引用BCircleService(後面簡稱B)B中引用A就構成了一個循環依賴,下面我們用示意圖來描述它們的創建過程
1、首先大家理解下面幾個容器
(1)singletonsCurrentlyInCreation:表示當前正在創建的bean(僅僅存放名字beanName),如果沒創建完成,都會保存在這裏面
(2)singletonObjects:一級緩存,可以理解爲bean最終都是放在這裏面的,一個bean真正完成了就放到這裏面
(3)earlySingletonObjects:二級緩存,過渡使用
(4)singletonFactories:三級緩存,也是過渡使用(提前曝光的bean存放),它與二級緩存不同的是它放的是ObjectFactory,而不是最終的Bean,二級緩存中是放的三級緩存getObject的結果
介紹完上面的容器,我們接着看在創建A,B時它是如何從上述的容器中變化
2、變化過程圖
假如先創建A,此時四個容器中都是空的
(1)先依次從一級、二級、三級緩存中判斷是否有能拿到A,結果顯然是拿不到,四個容器都是空的,我就不畫了
(2)要開始創建A,此時需要往singletonsCurrentlyInCreation放入A,表示A正在實例化,此時四個容器的狀態如下
(3)接下來正式開始創建A到A創建完成(堆上面已經分配了空間,但是屬性還沒賦值),此時將A封裝成ObjectFactory對象(爲什麼要封裝,後面會講一下),大家可以認爲此時的A對象已經創建,但是屬性未賦值,我們暫時用下面命名AObjectFactory,但是AobjectFactory.getObject() == A(A的地址假設A@9527xxx),此時A是在堆上已經創建好了,但是它的屬性是null(bCircleService==null),我不知道這裏有沒有描述清楚,容器狀態如下:
(4)此時要給A的屬性賦值,這裏就是給bCircleService賦值,那麼就去創建B,創建B的過程和創建A的過程一樣的,先依次從一級緩存、二級緩存、三級緩存中獲取B,顯然獲取不到,那麼正式開始創建B,singletonsCurrentlyInCreation中加入B,表示當前也正在創建B,容器狀態如下:
(5)接下來同(3)類似,B創建完成,此時也只是在堆上創建好了對象,但是B中的屬性aCircleService還沒有賦值(aCircleService==null),此時將B封裝成BObjectFactory放到三級緩存,容器狀態如下:
(6)接下來是重點了,此時開始給B的屬性賦值了,這裏即給aCircleService 賦值,那麼它就要去創建A,並且把A的內存地址付給B中的aCircleService屬性,那麼創建A的過程和之前的一樣,先依次從一級、二級、三級緩存中拿A,此時是可以從三級緩存中拿到A的,那麼將拿到的A賦值給B的aCircleService屬性,此時aCircleService==A@9527xxx,此時B即將創建完成了,在全部創建完的前一步,將三級緩存中的B移到二級緩存(存放的是BObjectFactory.getObject()),因爲實例化B的全部步驟全部做完了,此時容器的狀態如下:
(7)此時B(假設地址是B@9527)已經全部實例化完成了,但是還有一些收尾的工作呀,就是需要從當前正在創建的容器(singletonsCurrentInCreation)中移除(表示B創建完成),並且同時將B移動到一級緩存singletonObjects中,此時的容器狀態
(8)上述B已經創建完成了,我們要記得創建B的時機是在A給bCircleService賦值的時候,所以我們的邏輯又到了給A的屬性賦值的時候了,此時我們知道B已經創建完成了,所以bCircleService==B@9527,此時A的實例化也快要結束了,A也要將三級緩存的內容移到二級緩存,類似過程(6),容器狀態如下:
(9)最後A也要做一些結束的動作,類似過程(7),到這裏A和B都已經全部實例化完成了
總結:上述看上去流程挺多的,其實主要的核心就是在A創建完成(對象已經在堆中分配),還沒有給屬性賦值(bCircleService==null)的過程中,將A封裝成ObjectFactory,放到三級緩存。然後在實例化B的過程中,給B的屬性aCircleService賦值時,依次 從容器中拿A,此時是可以從三級緩存中拿到,所以不會再去走創建A的過程了,相當於提前曝光了A
上面還留了兩個問題,會在下面的源碼分析中解釋
(1)爲什麼spring-ioc中無法解決構造函數中的循環依賴
(2)爲什麼需要使用三級緩存,而且裏面裝的是ObjectFactory
三、源碼分析
1、AbstractBeanFactory#doGetBean()
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//(1).先依次從一級、二級、三級緩存中看看能否取到
Object sharedInstance = getSingleton(beanName);
//(2).如果緩存中能取到,則不會走下面的創建
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
//(3).如果緩存中取不到,則走創建的邏輯
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
//省略代碼...
// Create bean instance.
if (mbd.isSingleton()) {
//(4).主要的創建邏輯
sharedInstance = getSingleton(beanName, () -> {
try {
//(5).java8函數式編程
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//省略代碼...
}
//省略代碼...
return (T) bean;
}
上述代碼我省略了很多,主要保留了需要分析循環依賴的邏輯,上面已經加了註釋
首先從一級、二級、三級緩存中取
下面的if else針對是否能從緩存中取出結果
結合上述的圖,我們發現第一次創建A,顯然是走else的創建邏輯getSingleton
2、DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
//(1).開始創建之前調用方法
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
//(2).真正的調用方法
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
//(3).開始創建之後調用方法
afterSingletonCreation(beanName);
}
if (newSingleton) {
//(4).創建成功之後調用的方法
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
我在四個核心的方法上加了註釋
beforeSingletonCreation(beanName);
singletonFactory.getObject();
afterSingletonCreation(beanName);
addSingleton(beanName, singletonObject);
3、DefaultSingletonBeanRegistry#beforeSingletonCreation(beanName)
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
比較簡單,其實就是判斷當前正在創建的容器singletonsCurrentlyInCreation是否已經包含正在創建的類,如果包含,則拋異常,我們在構造做循環依賴就會在這裏拋異常,後面會具體分析爲什麼會在這裏拋異常
4、DefaultSingletonBeanRegistry#afterSingletonCreation(beanName);
這裏我們先沒有分析singletonFactory.getObject()創建的核心邏輯,因爲比較長一時半會兒分析不了,我們分析afterSingletonCreation是因爲它和上面的beforeSingletonCreation有關聯。這裏我們先假設singletonFactory.getObject()已經成功執行完了,我們看afterSingletonCreation()
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
很簡單,就是將singletonsCurrentlyInCreation清除當前正在創建的bean,因爲此時我們已經創建完了,接下來再接着看addSingleton
5、DefaultSingletonBeanRegistry#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);
}
}
很簡單,4、5的步驟對應我們的上述第二節示意圖(6)-(7)的過程。上面我們還沒有分析singletonFactory.getObject()創建bean的核心邏輯,只是假設它成功調用完成了,我們現在回過頭來分析
6、ObjectFactory#getObject()
這裏其實是調用的AbstractAutowireCapableBeanFactory#createBean(),因爲這裏是使用java8的lambda表達式,傳的是一個函數(參考1中代碼註釋(5)的那一段代碼)
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
//省略代碼....
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
//省略代碼...
}
省略了代碼,我們重點看doCreateBean,這個是核心
7、AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//(1).創建bean包裝類BealWrapper,這段代碼執行結束,說明bean已經構建完成,在堆上創建了實例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//(2).拿到bean
final Object bean = instanceWrapper.getWrappedInstance();
//省略部分代碼...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
//(3).這裏非常重要,沒有刪除原來的英文註釋,這裏就是提前曝光
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//(3-1)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//(4).給屬性賦值
populateBean(beanName, mbd, instanceWrapper);
//(5).調用初始化方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
if (earlySingletonExposure) {
//(6).bean在容器中移動
Object earlySingletonReference = getSingleton(beanName, false);
//省略部分代碼...
return exposedObject;
}
這裏是解決循環依賴的核心—即提前曝光一個實例(該實例已經創建好,但是裏面的屬性還沒賦值,因爲賦值的邏輯要到代碼(4),而提前曝光的邏輯在(3))。拿上面的循環依賴A,B來說,代碼執行完(3)時,A對象已經在堆上分配,只是bCircleService == null而已。此時將A提前曝光,插入到三級緩存中;而實例化B的入口則在(4)中,給bCircleservice賦值,此時如果B沒有創建,就開始創建B。等B同樣執行完上述(3),則B也在堆上分配了,只是暫時B中的aCircleService==null,所以B執行(4)時,去創建A,此時創建A先依次從一級、二級、三級緩存中取A時是可以在三級緩存中取到,因此代碼不會執行1中的else邏輯,而是執行if。
我們現在來處理上述的第一個問題:爲什麼構造函數中的循環依賴不能解決?
我們還是拿A,B來舉例(假如A的構造函數中依賴B),如果在構造函數中循環依賴,則A不會上述代碼7中的(3)而是在(1)中就去獲取B(此時注意A沒有提前曝光,即一級、二級、三級緩存中都不存在),假如此時B沒有創建,則開始創建B。等B執行到(4)時,給B中的aCircleService賦值時,需要去創建A,先從一級、二級、三級緩存中去取A,取不到,則走代碼1中的else邏輯,else的邏輯最終會調用beforeSingletonCreation(beanName),因此就拋異常了。
我們處理第二個問題,爲什麼需要使用三級緩存singletonFactories,裏面裝的是ObjectFactory,而不是直接將ObjectFactory.getObject()獲取的結果放到二級緩存earlySingletonObjects呢?
這裏我們就需要看上述代碼7中的(3-1)addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));這裏是使用java8的函數式編程,如果不明白的,我用下面的匿名內部類來替換把
addSingletonFactory(beanName, new ObjectFactory<Object>(){
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference();
}
});
我們再看看getEarlyBeanReference()方法
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
這裏無非就是對這個bean進行攔截,做一些處理,最終這個方法的調用時機,是在代碼2中的(4),此時bean的實例化的基本全部完成,所以這裏起到了延遲調用的作用。如果不延遲調用存在兩種情況
(1)不調用getEarlyBeanReference(),則有些實例創建沒有起到必要的攔截
(2)不延遲,那麼可能該實例僅僅是在堆上分配了,裏面屬性什麼都沒賦值,初始化方法也沒調用,可能我們用beanpostprocessor攔截也沒任何意義,達不到效果。
這裏大家可以思考一下這兩個問題,
1.當A構造函數中依賴B,而在B中依賴A不是構造函數依賴,會不會報錯
2.當A依賴B時不是構造函數依賴,而B依賴A時是構造函數依賴,會不會報錯
如果大家對這兩個問題都能回答正確,我相信是徹底理解了spring是如何解決循環依賴,如果回答錯誤,那麼還需要繼續看看