1 循環依賴介紹
循環依賴是指兩個或兩個以上bean互相持有對方最終形成閉環。比如A依賴B,B依賴C,C依賴A
循環依賴包括構造器依賴和屬性依賴
2 三級緩存解決循環依賴
2.1 spring創建Bean步驟
spring創建bean主要有3個步驟
1 createBeanInstance(實例化bean)
2 populateBean(裝配bean)
3 initializeBean(初始化bean)
發生循環依賴的時候主要是在第2步
2.2 三級緩存介紹
spring管理的對象默認是單例的,那麼肯定有一個地方來緩存這些對象。spring是通過三級緩存來緩存對象的
/** 一級緩存:bean name和bean實例的緩存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 三級緩存:對象工廠緩存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** 二級緩存:提前早期對象的緩存,還沒完成初始化 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
實例化bean之後會先走如下代碼
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);
}
}
}
這段代碼的作用是把實例化的bean從二級緩存移除,放到三級緩存
獲取bean的代碼
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中獲取對象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 如果一級緩存沒有並且這個對象正在創建則從二級緩存獲取
synchronized (this.singletonObjects) {
// 從二級緩存獲取bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 如果二級緩存也沒有則從三級緩存獲取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 如果從三級緩存獲取成功則把bean從三級緩存移到二級緩存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
當bean初始化完全後會放入一級緩存
2.3 三級緩存解決循環依賴
假如有一個場景A引用B,B引用A,三級緩存工作原理如下
1 實例化A對象
2 把還未完全初始化的A暴露到三級緩存singletonFactories
3 裝配bean
4 發現依賴B,就get B
5 B還沒有被創建,則進行創建
6 B依賴A,所以get A,由於第二步已經把A放到三級緩存,所以順利地拿到A,完成初始化,把自己放到一級緩存中
7 A順利地拿到B,完成初始化。
三級緩存只能解決通過屬性注入的循環依賴,不能解決通過構造函數注入的循環依賴。因爲把bean放入三級緩存的前提是執行構造函數
2.4 三級緩存的必要性
如果只解決循環依賴問題,一級緩存足矣。但是如果僅僅使用一級緩存,那麼該緩存裏存放的對象既有完全初始化的,又有不完全初始化的。
如果拿到不完全初始化的對象很容易出現NPE
有人說,那好,我再建一個緩存,一級緩存用來存完全初始化的bena,二級緩存用來存不完全初始化的bean,可以了吧。這樣是可以的,但是如果
一個對象被做成切面,那麼該對象就會生成一個代理對象。這樣依賴注入的bean仍是原始的bean,spring會拋異常。
爲了解決代理對象注入的問題,加個三級緩存,裏面不存具體的bean,裏面存一個工廠對象。通過工廠對象,是可以拿到最終形態的代理後的bean
如果想深入瞭解三級緩存的必要性可以參考這位大佬的文章:spring解決循環依賴爲何用三級緩存