循環依賴就是N個類之間循環嵌套引用,如A依賴B,B又依賴A,你中有我,我中有你。實例化A時發現須要B屬性,因而去實例化B,發現須要A屬性。。。若是Spring不對這種循環依賴進行處理程序就會無限執行,致使內存溢出、系統崩潰。java
循環依賴又分爲構造器循環依賴和屬性循環依賴,因爲Spring不支持構造器循環依賴,會直接報錯,因此接下來只討論屬性循環依賴。緩存
Bean實例化能夠大體分爲三步bash
其中循環依賴發生在實例化和注入屬性這兩個步驟裏。app
實例化A時,將A這個並不完整的對象緩存起來,這樣當B實例化後,注入A的時候,可以從容器中獲取到A對象,完成初始化。最後將B對象注入到A中,A完成初始化。ide
Spring引入了三級緩存來解決循環依賴的問題ui
/** 一級緩存,緩存初始化完成的bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
/** 二級緩存,緩存原始bean(還未填充屬性),用於解決循環依賴 */
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
/** 三級緩存,緩存bean工廠對象,用於解決循環依賴 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);複製代碼
當獲取單例bean時,會依次訪問一級緩存、二級緩存、三級緩存,緩存命中則返回。this
getBean是從容器中獲取bean的入口方法,它裏面又調用了doGetBean方法,來看一下這個方法。spa
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 嘗試經過bean名稱獲取目標bean對象,好比這裏的A對象
Object sharedInstance = getSingleton(beanName);
// 咱們這裏的目標對象都是單例的
if (mbd.isSingleton()) {
// 這裏就嘗試建立目標對象,第二個參數傳的就是一個ObjectFactory類型的對象,這裏是使用Java8的lamada
// 表達式書寫的,只要上面的getSingleton()方法返回值爲空,則會調用這裏的getSingleton()方法來建立
// 目標對象
sharedInstance = getSingleton(beanName, () -> {
try {
// 嘗試建立目標對象
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
}複製代碼
這個方法裏面有兩個名稱爲getSingleton的方法,第一個getSingleton是從緩存中查找bean,若是緩存未命中,則走第二個getSingleton,嘗試建立目標對象並注入依賴。code
當第一次調用doGetBean獲取A對象,第一個getSingleton返回空,進入第二個getSingleton建立A對象,注入B對象。調用doGetBean獲取B,第一個getSingleton返回空,進入第二個getSingleton建立B對象,獲取並注入原始A對象,此時B對象初始化完成。最後將B對象注入A中,A完成初始化。cdn
先看第一個getSingleton的源碼
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 嘗試從緩存中獲取成品的目標對象,若是存在,則直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 若是緩存中不存在目標對象,則判斷當前對象是否已經處於建立過程當中,在前面的講解中,第一次嘗試獲取A對象
// 的實例以後,就會將A對象標記爲正在建立中,於是最後再嘗試獲取A對象的時候,這裏的if判斷就會爲true
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 這裏的singletonFactories是一個Map,其key是bean的名稱,而值是一個ObjectFactory類型的
// 對象,這裏對於A和B而言,調用圖其getObject()方法返回的就是A和B對象的實例,不管是不是半成品
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 獲取目標對象的實例
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}複製代碼
首先從一級緩存singletonObjects獲取目標對象,若不存在且目標對象被標記爲建立中,從二級緩存earlySingletonObjects中獲取bean。若是不存在,繼續訪問三級緩存singletonFactories,獲得bean工廠對象,經過工廠對象獲取目標對象。將目標對象放入二級緩存,刪除三級緩存。
第二個getSingleton方法
getSingleton(String beanName, ObjectFactory<?> singletonFactory)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// ......
// 調用 getObject 方法建立 bean 實例
singletonObject = singletonFactory.getObject();
newSingleton = true;
if (newSingleton) {
// 添加 bean 到 singletonObjects 緩存中,並從其餘集合中將 bean 相關記錄移除
addSingleton(beanName, singletonObject);
}
// ......
// 返回 singletonObject
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}複製代碼
該方法的主要邏輯是調用singletonFactory的getObject()方法建立目標對象,而後將bean放入緩存中。
接下來看一下getObject方法的實現,因爲在doGetBean中調用getSingleton時第二個參數是用匿名內部類的方式傳參,實際上調用的是createBean方法,而createBean又調用了doCreateBean。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
BeanWrapper instanceWrapper = null;
// ......
// ☆ 建立 bean 對象,並將 bean 對象包裹在 BeanWrapper 對象中返回
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 從 BeanWrapper 對象中獲取 bean 對象,這裏的 bean 指向的是一個原始的對象
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
/*
* earlySingletonExposure 用於表示是否」提早暴露「原始對象的引用,用於解決循環依賴。
* 對於單例 bean,該變量通常爲 true。更詳細的解釋能夠參考我以前的文章
*/
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// ☆ 添加 bean 工廠對象到 singletonFactories 緩存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
/*
* 獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執行 AOP
* 相關邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回
* bean,因此你們能夠把
* return getEarlyBeanReference(beanName, mbd, bean)
* 等價於:
* return bean;
*/
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
Object exposedObject = bean;
// ......
// ☆ 填充屬性,解析依賴
populateBean(beanName, mbd, instanceWrapper);
// ......
// 返回 bean 實例
return exposedObject;
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 將 singletonFactory 添加到 singletonFactories 緩存中
this.singletonFactories.put(beanName, singletonFactory);
// 從其餘緩存中移除相關記錄,即便沒有
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}複製代碼
方法的主要邏輯以下
Spring在實例化bean時,會先建立當前bean對象,放入緩存中,而後以遞歸的方式獲取所依賴的屬性。當注入屬性時,若是出現了循環依賴則會從緩存中獲取依賴對象。