[toc]
循環(huán)依賴
循環(huán)依賴就是N個(gè)類中循環(huán)嵌套引用,如果日常開發(fā)中我們用new對象的方式發(fā)生這種循環(huán)依賴的程序運(yùn)行一直循環(huán)直到內(nèi)存溢出報(bào)錯(cuò),下面說一下Spring是如何解決的:
首先循環(huán)依賴處理有三種情況:
- 構(gòu)造器的循環(huán)依賴,這種依賴Spring處理不了,直接拋出BeanCurrentlyInCreationException異常
- 單例模式下的setteer循環(huán)依賴:通過三級緩存處理循環(huán)依賴
- 非單例循環(huán)依賴:無法處理,拋出BeanCurrentlyInCreateionException異常
Spring單例對象的初始化大略分為三步:
- createBeanInstance:實(shí)例化,其實(shí)也就是調(diào)用對象的構(gòu)造方法實(shí)例化對象
- populateBean:填充屬性,這一步主要是多bean的雨來屬性進(jìn)行填充
- initializeBean:調(diào)用配置的init方法
從上面講述的單例Bean初始化步驟,可以知道,循環(huán)依賴主要發(fā)生在第一步、第二步,也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴。
Spring處理三種循環(huán)依賴
構(gòu)造器循環(huán)依賴
this.singletonsCurrentlyCreation.add(beanName)將當(dāng)前要?jiǎng)?chuàng)建的Bean記錄在緩存中,Spring容器將每一個(gè)正在創(chuàng)建的Bean標(biāo)識符放在當(dāng)前創(chuàng)建的Bean池中,bean標(biāo)志:在創(chuàng)建過程中,將保持在這個(gè)池中,因此創(chuàng)建Bean過程中發(fā)現(xiàn)自己已經(jīng)在創(chuàng)建當(dāng)前Bean池里,將拋出BeanCurrentlyIncreationException異常表示循環(huán)依賴,而對創(chuàng)建完畢的Bean將從當(dāng)前Bean池中清除掉。
單例模式下,setter循環(huán)依賴
Spring為了解決單例的循環(huán)依賴問題,使用了三級緩存:
//Cache of singleton objects: bean name ---> bean instance
private final Map<String,Object> singletonObjects=new ConcurrentHashMap(256);
//Cache of early singleton objects: bean name --->bean instance
private final Map<String,Object> earlySingletonObjects=new HashMap(16);
//Cache of singleton factories: bean name--->ObjectsFactory
private final Map<String,ObjectFactory<?>> singletonFacrories=new HashMap(16);
從字面的意思來說:
- singletonObjects:指單例對象的Cache,完成初始化單例對象的Cache(一級緩存)
- earlySingletonObjects:只提前曝光的單例對象的CCache,完成實(shí)例化但是尚未初始化的,提前曝光的單例對象的Cache(二級緩存)
- singletonFactories:指單例對象工廠的Cache,進(jìn)入實(shí)例化階段的單例對象工廠的Cache(三級緩存)
以上三個(gè)Cache構(gòu)成了三級緩存,Spring就用這三級緩存解決了循環(huán)依賴的問題。
創(chuàng)建Bean的時(shí)候,會(huì)首先從cache中獲取這個(gè)Bean,這個(gè)緩存就是sigletonObjects。
主要調(diào)用方法是:DefaultSingletonBeanRegistry類下面的getSingleton()方法:
/**
* 方法作用 返回返回以給定名稱注冊的(原始)單例對象。
* 描述:檢查已經(jīng)實(shí)例化的單例,還允許對當(dāng)前創(chuàng)建的單例的早期引用(解析循環(huán)引用)
* beanName:要?jiǎng)?chuàng)建的Bean的名稱
* allowEarlyReference:是否應(yīng)創(chuàng)建早期參考
* return 注冊的單例對象;如果找不到,則為null
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
//isSingletonCurrentlyInCreation,判斷當(dāng)前單例的Bean是否正在創(chuàng)建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
//allowEarlyReference,是否允許從singletonFactories中通過getObject拿到對象
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//從singletonFactories中移除,并放入earlySingletonObjects中
//其實(shí)就是從三級緩存移動(dòng)到二級緩存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
從上面緩存的分析,可以知道,Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個(gè)三級Cache,這個(gè)Cache的類型是ObjectFactory,定義如下:
public interface ObjectFactory<T> {
/**
* 返回一個(gè)實(shí)例可能是共享的或獨(dú)立的,由該工廠管理的對象
* @return 結(jié)果的實(shí)例
* @throws BeansException 創(chuàng)建錯(cuò)誤拋出異常
*/
T getObject() throws BeansException;
}
這個(gè)接口在AbstractAutowireCapableBeanFactory里的實(shí)現(xiàn),并在核心方法doCreateBean()引用了下面方法:
/**
*如果有必要添加用于生成指定單例的給定單例工廠
* 要求對單例登記,例如能夠解決循環(huán)引用
* @param beanName bean的名字
* @param singletonFactory 單例對象的工廠
*/
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);
}
}
}
這段代碼發(fā)生在createBeanInstance之后,populatBean()之前,也就是單例對象此時(shí)已經(jīng)被創(chuàng)建處理(調(diào)用了構(gòu)造器),這個(gè)對象已經(jīng)被生產(chǎn)出來了,此時(shí)將對象提前曝光出來,讓大家使用。
這樣的好處A的某個(gè)field或者setter依賴了B的實(shí)例對象,同時(shí)B的某個(gè)filed或者seteer依賴了A的實(shí)例對象,這種循環(huán)依賴的情況,A首先先完成初始化的第一步,并且將自己提前曝光到singletonFactories中,此時(shí)進(jìn)行初始化第二步,發(fā)現(xiàn)自己依賴對象B,此時(shí)將嘗試get(B),發(fā)現(xiàn)B沒有被創(chuàng)建,所以走create流程,B在初始化第一步的時(shí)候發(fā)現(xiàn)自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒用,因?yàn)锳還沒有初始化完全),嘗試調(diào)用二級緩存earltSingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectsFactory將自己提前曝光了,所以B拿到A對象后順利完成了初始化階段的1,2,3初始化之后將自己放到已經(jīng)緩存中singletonObjects中,此時(shí)返回A中,A此時(shí)拿到了B的對象順利完成自己的初始化節(jié)點(diǎn)2,3最終A也完成了初始化,放進(jìn)了以及緩存singletonObjects中,而且更加幸運(yùn)的是,由于B拿到了A對象引用,所以B現(xiàn)在持有A對象完成了初始化。
非單例循環(huán)依賴
對于prototype作用域bean,Spring容器無法完成依賴注入,因?yàn)镾pring容器不進(jìn)行緩存prototype作用域的Bean,因此無法提前暴露一個(gè)創(chuàng)建中的Bean
總結(jié)
只有單例的Bean才能解決循環(huán)依賴問題。
首先完成創(chuàng)建Bean過程的第一步時(shí)候createBeanInstance,將已經(jīng)調(diào)用了構(gòu)造器但尚未進(jìn)行填充屬性的bean放入三級緩存中即singletonFactories,提前曝光出來,當(dāng)創(chuàng)建Bean進(jìn)行第二步時(shí)populateBean,填充屬性,當(dāng)把屬性填充成功時(shí)候,將從三級緩存中移除,放入二級緩存中,即earlySingletonObjects,當(dāng)進(jìn)行第三步initializeBean,Bean創(chuàng)建成功,將完全創(chuàng)建成功的Bean放入一級緩存中singletonObjects,Bean完成創(chuàng)建成功。