關於spring循環依賴說明
什麼是循環依賴
多個對象相互持有對方引用形成一個閉環。
舉例:
用一個簡單舉例,A依賴B,B依賴A情況。A–>B–>A
@Component
pubilc Class A{
@Autowired
private B beanB;
// getter & setter
}
@Component
public Class B{
@Autowired
private A beanA;
// getter & setter
}
spring解決循環依賴的思路
不管當前的bean有沒有循環依賴,提前實例化,緩存剛實例化的bean,接着填充屬性,再緩存完成填充屬性的bean,此時的bean實例可用
爲什麼獲取bean的對象的時候會出現實例化、再填充屬性兩部分,而不是一步完成呢?
可以簡單理解爲實例化就是先在內存中佔個位子,即一個地址引用。spring在依賴注入的時候, 實際傳遞的是一個對象引用。在被依賴的時候,可以直接引用剛實例化的bean, 達到中斷了被依賴的bean對它自己屬性填充,從而避免了循環的發生。
以上述爲例:當前beanA實例化,緩存實例化的beanA, 開始填充屬性beanA,填充屬性過程中發現依賴beanB,於是開始beanB的實例化,緩存實例化後的beanB, 接着開始填充屬性beanB,填充屬性的過程中發現依賴beanA,於是在緩存中找到了剛實例化的beanA, 然後依賴注入beanA,此時注入的是beanA的引用且不可用,這樣beanB的填充屬性就完成了。beanB的填充屬性完成後,beanB實例就可用了,接着beanA完成了對beanB的依賴注入後,beanA自身的填充屬性也完成了,beanA實例也可用了。那麼此時beanB中的屬性beanA是對象引用且可用。
簡圖描述:
如圖示,在beanB依賴注入beanA時,如果沒有將剛實例化的beanA放入緩存中,那麼必然導致beanA實例化,填充屬性的時候因爲依賴beanB,又會實例化beanB,填充屬性從而無限循環下去。而開始實例化beanB,填充屬性的過程中發現依賴beanA,於是在緩存中找到了剛實例化的beanA,依賴注入beanA,此時注入的是beanA的對象引用,這樣中斷了在beanB中因爲依賴beanA,再次出現beanA的實例化、填充屬性的循環;這就是在getBean的時候爲什麼有實例化、填充屬性兩部分。
spring解決循環依賴藉助了:提前實例化、填充屬性兩步,以及緩存。理解這些,有助於分析spring源碼中對循環依賴的處理
spring能解決哪些循環依賴
spring 只解決單例setter循環依賴,對於原型模式,單例構造器注入的循環依賴沒有解決,直接拋異常,spring中沒有對原型模式的單例進行緩存,
在spring解決循環依賴的思路小結中理解了spring解決循環依賴藉助了對象實例化、填充屬性兩步、緩存。那麼就很容易理解spring爲什麼不能解決構造器注入的循環依賴。構造器在實例化階段就需要持有對方引用,此時對象無法創建,也不存在。而對於原型模式的循環依賴,因爲spring不緩存原型模式下的bean實例,所以也無解。
bean加載的初步流程
在理解了上述循環依賴說明後,再理解bean加載的循環依賴,只需要對bean加載有一個全局瞭解即可,所謂全局瞭解,不需要知道bean的加載詳細的細節,關鍵是理解bean加載過程中幾個重要的流程步驟。何時初始化bean、何時緩存bean、何時實例化bean.接下來看bean如何加載的
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
bean加載的邏輯
獲取bean的邏輯主要在doGetBean中完成,現在將代碼重要邏輯流程,梳理爲邏輯代碼,有個全局的思路,
注意:僞代碼,注重bean的重要流程,去掉不重要代碼,例如部分判斷,try catch,
將用戶輸入的name裝爲beanName.爲什麼要轉?name可能爲bean的別名,也可能是&開頭的名稱
(重要)從緩存中獲取單例
if(緩存中存在){
(重要)轉爲用戶需要的最終實例,
}else{
如果緩存中沒有,那麼正常創建流程
if(當前bean爲原型模式且創建中) 拋異常,循環依賴
當前容器沒有bean信息,存在父類容器,那麼從父類中獲取bean信息
標記已經創建的bean
將xml配置存儲的BeanDefinition轉爲RootBeanDefinition,如果beanName是子類,需要同時合併父類的相關屬性,爲什麼要合併轉爲RootBeanDefinition?
depends-on的處理
根據不同作用域創建bean實例
if(單例){
(重要)獲取bean
轉爲用戶需要的最終實例,爲什麼要轉?此處獲取的bean可能是FactoryBean實例,那麼需要轉爲FactoryBean.getObject()生產實例,也可能是用戶需要的bean實例,直接返回
}else if(原型模式){
記錄原型模式下,當前bean爲創建中狀態
創建bean實例
撤銷當前bean爲創建中狀態
轉爲用戶需要的最終實例
}else{
其他作用域bean的實例化,邏輯同原型模式
}
}
如果有提供bean類型,則進行類型轉換
返回最終bean
bean加載的僞代碼的實現
根據加載邏輯,在看看粗糙僞代碼的獲取bean的過程
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 提取對應的beanName
// 如果是FactoryBean,則去掉&前綴,再查看去掉&之後的name(可能爲別名)是否有對應的beanName
final String beanName = transformedBeanName(name);
// 獲取單例緩存中實例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 返回對應的實例,已經生成了當前bean的實例,爲什麼不直接返回?
//當前實例bean可能是一個普通的bean,也可能是一個FactoryBean的實例,
// 如果是普通bean的實例,則直接返回,
//如果是FactoryBean的實例並且請求的name前面有&修飾,則說明客戶端想要獲取一個工廠的實例,那麼直接返回
//如果是FactoryBean的實例並且請求的name前面沒有&修飾,則要獲取的工廠生產的bean的實例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} eles {
//只有在單例setter依賴注入情況纔會嘗試解決循環依賴,原型模式下,無解,不做處理,直接拋異常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
//如果當前容器中不存在beanName,嘗試從父容器parentBeanFactory中獲取
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 省略邏輯
}
if (!typeCheckOnly) {
// 標記,將當前bean標記爲已創建,存放到alreadyCreated緩存池中,並且需要重新mergedBeanDefinitions
markBeanAsCreated(beanName);
}
//將xml配置存儲的BeanDefinition轉爲RootBeanDefinition,如果beanName是子類,需要同時合併父類的相關屬性
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// 如果存在depends-on,則需要遞歸實例化依賴的bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// 緩存當前beanName的依賴
registerDependentBean(dep, beanName);
// 對當前bean的依賴,進行實例化
getBean(dep);
}
}
// 實例化依賴的bean後,可以實例mgd本身了
if (mbd.isSingleton()) {
// 創建單例bean
sharedInstance = getSingleton(beanName, () -> {
//創建bean
// getSingleton(String beanName, ObjectFactory<?> singletonFactory)創建的實例,
// 最終依賴於singletonObject = singletonFactory.getObject();,也就是依賴singletonFactory,
// 而singletonFactory.getObject()實現是createBean,
return createBean(beanName, mbd, args);
});
// sharedInstance已經完成了初始化,屬性填充
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
}
// bean的類型匹配
if (requiredType != null && !requiredType.isInstance(bean)) {
// 代碼略
}
return (T) bean;
}
注意:markBeanAsCreated(beanName)中使用的alreadyCreated緩存池,在實例創建完成後,需要藉助alreadyCreated緩存池做循環依賴檢查
經過上述的流程,我想應該對bean實例化整體流程有了清晰的認識,
分析從緩存中獲取bean的實例
現在具體深入瞭解下單例模式下,如何實例化bean
首先看看從緩存中獲取實例
public Object getSingleton(String beanName) {
// 注意,第二個參數是否允許早期依賴循環引用
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// bean創建過程中藉助了幾個單例緩存池,如:singletonObjects、singletonsCurrentlyInCreation、earlySingletonObjects、singletonFactories
// 獲取單例緩存中實例
// 在已經創建完成的單例bean的緩存中,獲取當前bean是否已經創建
Object singletonObject = this.singletonObjects.get(beanName);
// 如果當前bean實例還未創建,並且當前bean正在創建中
// isSingletonCurrentlyInCreation(beanName)中使用邏輯,使用了singletonsCurrentlyInCreation緩存池
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從正在創建中bean(此時bean已經初始化,且完成了屬性填充,但還未暴露)獲取bean實例
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果沒有並且當前單例bean允許早起循環引用
if (singletonObject == null && allowEarlyReference) {
// 從單例工廠緩存中獲取創建bean的單例工廠,由單例工廠產生bean,問題:爲什麼存放單例工廠,而不直接是bean呢?
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 單例工廠不爲空,則創建bean,並放入到
singletonObject = singletonFactory.getObject();
//此時singletonObject是一個早期剛初始化的bean,但並未填充屬性,s屬性擴展,代理等
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
實現邏輯,首先查找singletonObject單例緩存池,沒有發現,此時bean還未創建成功,接着根據條件判斷,沒有在singletonObject緩存池中獲取到實例且當前bean正在創建中,則從earlySingletonObjects緩存池中獲取(this.earlySingletonObjects.get(beanName)),發現仍然沒有, 接着從singletonFactories緩存池中查找,發現不爲空並且允許早起循環引用,於是根據當前工廠獲取bean實例,並將實例添加到earlySingletonObjects緩存池中,並從singletonFactories單例緩存池中移除
單例緩存池介紹
上述代碼中出現了幾個單例緩存池,每個單例緩存池作用,以及爲什麼需要這麼多的單例緩存池呢?
首先看看每個單例緩存池的作用
- singletonObjects: 存放已經初始化的單例bean的實例
- earlySingletonObjects: 存放工廠bean剛創建bean實例,此時實例還未填充屬性,代理、自定義業務邏輯等,
- singletonFactories:存放單例bean的工廠(用於創建單例)
- singletonsCurrentlyInCreation: 存放當前正在創建中的單例bean
- registeredSingletons: 存放所有實例化的bean
由上述的代碼可以看出,查詢緩存池順序爲:
|----bean創建完成--|----------bean創建中--------------------|
singletonObjects-->earlySingletonObjects-->singletonFactories
根據查詢邏輯推斷,bean創建過程中存放到單例緩存池的順序依次:
|----------------創建中---------------|------創建完成-------|
singletonFactories-->earlySingletonObjects-->singletonObjects
上述單例緩存中查找單例邏輯,爲什麼這麼實現?
主要是爲了解決單例循環依賴,那麼是如何藉助這些單例緩存池解決循環依賴?
爲了回答這個問題,我們必須明白什麼時候用這些緩存池?接下里看看在哪些邏輯上使用了這些緩存池。是否如上述推斷單例bean存放到緩存池中的順序呢?
加載bean過程中是如何應用單例緩存池
回到主邏輯上,如果首次創建bean,那麼肯定沒有緩存,需要走正常創建bean的邏輯,找到單例模式下,創建bean的代碼
sharedInstance = getSingleton(beanName, () -> {
try {
//創建bean
// getSingleton(String beanName, ObjectFactory<?> singletonFactory)創建的實例,
// 最終依賴於singletonObject = singletonFactory.getObject();,也就是依賴singletonFactory,
// 而singletonFactory.getObject()實現是createBean,
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// 異常處理
throw ex;
}
});
可以看出有兩個參數,一個是beanName,一個是匿名的ObjectFactory類,其中匿名內部類中的有個回調方法ObjectFactory.getObject(),這個方法中的createBean(beanName, mbd, args)方法似乎實現了創建bean的邏輯,此處採用Lambda 表達式,那麼getSingleton實現邏輯是什麼呢?詳細看看
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 記錄beanName爲正在加載狀態 this.singletonsCurrentlyInCreation.add(beanName)
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
// 獲取的可能是bean的實例,也可能是創建實例的ObjectFactory的實例
// singletonFactory.getObject()的實現邏輯參考createBean(beanName, mbd, args)
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (Exception ex) {
// 省略異常處理
throw ex;
} finally {
// bean創建完成後,
// 移除beanName爲正在加載狀態 this.singletonsCurrentlyInCreation.remove(beanName)
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 將實例放入singletonObjects、registeredSingletons緩存中,
// 並刪除加載bean過程中的所記錄的輔助狀態 singletonFactories、earlySingletonObjects
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
從上面代碼可以看出getSingleton方法有兩個參數,beanName和ObjectFactory(創建bean的具體實現),
主要邏輯:
1、記錄當前bean爲正在創建狀態
將當前正在創建中的bean添加到this.singletonsCurrentlyInCreation單例緩存池
2、回調匿名內部類ObjectFactory#getObject()方法創建實例,
創建bean過程中將創建bean的工廠添加到this.singletonFactories單例緩存池、
同時將當前bean添加到this.registeredSingletons單例緩存池中,標記bean已創建
此處只需要知道這一步操作裏面有這些單例緩存池的應用場景,可以跳過閱讀下面詳細解釋。如果需要知道singletonFactories、registeredSingletons單例緩存池的詳細應用,參見下方創建bean準備工作,
回調匿名內部類ObjectFactory#getObject()方法創建實例具體邏輯,此方法中涉及到singletonFactories單例緩存池、registeredSingletons單例緩存池的應用,看看是如何應用的?
創建bean準備工作
對getSingleton匿名內部類回調方法代碼的簡化處理
singletonObject = singletonFactory.getObject(){
createBean(){
doCreateBean(){
// 實例化bean,將BeanDefinition轉爲BeanWrapper
instanceWrapper = createBeanInstance(beanName, mbd, args);
// bean合併後,應用後處理器,如:Autowired註解正式通過此方法實現諸如類型的預解析
// AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
// 是否允許單例提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 單例&允許循環依賴&正在創建中的單例
if(earlySingletonExposure){
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){
// 將創建bean的singletonFactory,放入緩存
// getEarlyBeanReference,對bean再一次依賴引用,主要應用SmartInstantiationAwareBeanPostProcessor
// 我們熟悉的AOP就是在這裏將advice動態織入bean中,如果沒有,則直接返回bean,不做處理
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
// bean的屬性填充,
populateBean(){
// 這裏會將當前bean所需的依賴進行實例化,getBean(propertyValue),並填充
// 舉例A類某字段依賴B,那麼在A還未完成實例化前,在填充屬性階段,會優先實例化A的字段B,此時Abean存儲在singletonFactories中
// 如果恰好B類中某字段依賴A,循環出現了,如何解決?
// 還記得上述首次獲取單例的代碼?依次從singletonObjects-->earlySingletonObjects-->singletonFactories
// 這樣A類的依賴B就可以拿到自己的依賴A的實例,此處實例不是完全的,但與最終單例A的地址是一樣的,當A實例完成後,那麼B類中引用的A的實例也是全部完成實例化的
}
// 通過bean初始化進行擴展
initializeBean(){
// 爲當前bean獲取一些相對應的資源,調用Aware,如BeanFactoryAware,
invokeAwareMethods
// 在調用客戶自定義初始化前,利用BeanPostProcessors自定義擴展,
applyBeanPostProcessorsBeforeInitialization
// 調用用戶自定的初始化邏輯
invokeInitMethods{
// 優先調用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet()
// 調用用戶自定義的init-method
invokeCustomInitMethod(beanName, bean, mbd);
}
// 在調用客戶自定義初始化後,利用BeanPostProcessors自定義擴展
applyBeanPostProcessorsAfterInitialization
}
// 對已經實例化的bean進行循環依賴檢查
if(earlySingletonExposure){
// 查看當前bean是否被詢依賴,當前bean正在創建中,不會出現在singletonObjects緩存中,
// 假設當前bean沒有被依賴,則當前bean實例一定在singletonFactories中,
// 如果被依賴,則當前bean實例因爲需要加載,則會在緩存中查找,當查找後,會將singletonFactories中的工廠生產的實例存放在earlySingletonObjects單例緩存中,
// 爲了檢測是否有循環依賴,只需要查詢earlySingletonObjects單例緩存中是否存在當前bean的實例,那麼參數allowEarlyReference爲false
Object earlySingletonReference = getSingleton(beanName, false){
// 省略相關單例緩存從中獲取實例的邏輯,詳情參看getSingleton()代碼
if (singletonObject == null && allowEarlyReference) {
// 將創建bean的singletonFactory,放入緩存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
// 如果earlySingletonReference不爲空,則發生了循環依賴,
// 那麼需要檢查當前實例與初始化後的實例是否一樣,
// 如果一樣,則可以直接返回使用,
// 如果不一樣,則需要檢查是否有未完成依賴的屬性
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 獲取當前bean的依賴
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 如果當前依賴並沒有實例化,沒有出現在已經創建實例的bean緩存中(!this.alreadyCreated.contains(beanName))
// 什麼時候將bean記錄到alreadyCreated中呢?參考AbstractBeanFactory.doGetBean-->AbstractBeanFactory.markBeanAsCreated
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 如果存在沒有實例化的依賴,則說明當前bean實例化失敗,
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// 註冊DisposableBean,如果配置了destroy-method,這裏需要註冊,便於在銷燬bean調用
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
}
}
總結doCreateBean處理邏輯流程:
- 2.1、實例化bean,將BeanDefinition轉爲BeanWrapper
- 2.2、bean合併後的後處理器
- 2.3、允許單例提前暴露,將生產bean的bean工廠添加到singletonFactory單例緩存中
- 2.4、bean的屬性填充,
- 2.5、通過bean初始化進行擴展
- 2.6、對已經實例化的bean進行循環依賴檢查
- 2.7、註冊DisposableBean,銷燬bean時回調
對於單例循環依賴, 我們最關注的是第3、6步,第3步,將創建bean的bean工廠添加到singletonFactory單例緩存中,提前暴露單例,第6步、查看當前已經初始化的bean的是否有循環依賴
3、移除當前bean創建中狀態
將當前正在創建中的bean從singletonsCurrentlyInCreation單例緩存池中移除
4、緩存當前創建完後的bean實例
將當前bean實例添加到singletonObjects單例緩存中
至此,涉及到的幾個單例緩存池已經全部用到了,那麼是如何解決單例setter循環依賴呢?
解決循環依賴
用一個簡單舉例,A依賴B,B依賴A情況,分析他們的加載過程,來回顧一下bean加載過程中,幾個單例緩存池的應用,以及如何解決循環依賴的
@Component
pubilc Class A{
@Autowired
private B b;
// getter & setter
}
@Component
public Class B{
@Autowired
private A a;
// getter & setter
}
首次加載A的時候,沒有緩存,先將beanA放入到singletonsCurrentlyInCreation單例緩存池,接着講創建A的工廠放入到singletonFactories單例緩存池中,接着實例A在填充它的屬性B的時候發現依賴B,於是getBean(beanB), 開始加載B
首次加載B的時候,沒有緩存,先將beanB放入到singletonsCurrentlyInCreation單例緩存池,接着將創建B的工廠放入到singletonFactories單例緩存池中,接着實例B在填充它的屬性A的時候發現依賴A,於是getBean(beanA), 開始加載A
第二次加載A,由於之前已經放到單例緩存池中了,所有進入緩存中查找,執行getSingleton(String beanName)–>getSingleton(beanName, true)的邏輯,第二參數允許早起循環引用,首先查找singletonObject單例緩存池,發現沒有,因爲此時beanA還未創建成功,接着根據條件判斷,沒有在singletonObject緩存池中獲取到實例且當前beanA正在創建中,所以從earlySingletonObjects緩存池中獲取(this.earlySingletonObjects.get(beanName)),發現仍然沒有, 接着從singletonFactories緩存池中查找,發現不爲空並且允許早起循環引用,於是根據當前工廠獲取bean實例,並將實例添加到earlySingletonObjects緩存池中,並從singletonFactories單例緩存池中移除,
由於B的屬性填充過程中獲取到A的實例,雖然此時B屬性中的實例A剛初始化,還未填充屬性,但是它的地址和A的實例地址一樣。這樣就中斷了對實例A的屬性填充,從而避免了循環依賴
B完成屬性填充後,進行初始化,進行依賴循環檢查,則,B初始化完成,從singletonsCurrentlyInCreation單例緩存池中移除beanB,將beanB存放到singletonObjects,並記錄beanB到registeredSingletons單例緩存池中,beanB已經實例化,那麼接着繼續A的屬性填充,當A完成屬性填充,初始化後,從singletonsCurrentlyInCreation單例緩存池中移除beanA,將beanB存放到singletonObjects,並記錄beanA到registeredSingletons單例緩存池中,標識beanA已經實例化。
至此整個與循環依賴的加載完成。理解了bean加載過程中的循環依賴,那麼對於bean的加載,我們就更加清晰