Spring的循環(huán)依賴,到底是什么樣的
前一段時(shí)間,阿粉的讀者給阿粉留言,說在面試的時(shí)候,有個(gè)面試官就問她,Spring 的各種知識(shí),Spring 的生命周期, Spring 的循環(huán)依賴是如何解決的。
就這么幾個(gè)問題,雖然回答的不是很好,但是也是很幸運(yùn)的接到了 offer ,畢竟面試一般很少會(huì)因?yàn)橐粌蓚€(gè)面試題回答的不好,就直接 pass 的,還是看綜合表現(xiàn)的,既然問到阿粉這個(gè) Spring 是如何處理循環(huán)依賴的了,那么阿粉就得來解釋一下,Spring 是如何處理循環(huán)依賴的。
循環(huán)依賴
什么是循環(huán)依賴,說到循環(huán)依賴,這個(gè)實(shí)際上是沒有那么復(fù)雜的,就比如很簡(jiǎn)單的說,A 引用了 B ,而這個(gè)時(shí)候 B 也引用了 A ,那么這種情況實(shí)際上就是出現(xiàn)了循環(huán)依賴的問題了,實(shí)際上也可以把循環(huán)依賴稱之為循環(huán)引用,兩個(gè)或者兩個(gè)以上的bean互相持有對(duì)方,最終形成閉環(huán)。
這就是循環(huán)依賴,也就是循環(huán)引用,
注意,這里不是函數(shù)的循環(huán)調(diào)用,是對(duì)象的相互依賴關(guān)系。循環(huán)調(diào)用其實(shí)就是一個(gè)死循環(huán),除非有終結(jié)條件。否則的話,他就是一個(gè)死循環(huán).
Spring 中的循環(huán)依賴
那么 Spring 的循環(huán)依賴都有什么呢?
- 構(gòu)造器的循環(huán)依賴
- field屬性的循環(huán)依賴
那么針對(duì)這兩種循環(huán)依賴,Spring 它是如何解決的呢?這就很特殊了,構(gòu)造器的循環(huán)依賴問題實(shí)際上算是個(gè)無解的操作,只能拋出 BeanCurrentlyInCreationException 異常,也就是說,這個(gè)構(gòu)造器導(dǎo)致的循環(huán)依賴,Spring 是沒有辦法來處理的,也只是給拋出了異常,但是對(duì)于 字段屬性 的循環(huán)依賴,還是有解決辦法的。
Spring怎么解決循環(huán)依賴
這個(gè)時(shí)候,我們就得看看 Spring 的對(duì)象初始化的過程了,
Spring的單例對(duì)象的初始化主要分為三步:
- createBeanInstance 實(shí)例化
- populateBean 填充屬性
- initializeBean 初始化
createBeanInstance 實(shí)例化實(shí)際上就是調(diào)用對(duì)象的構(gòu)造方法實(shí)例化對(duì)象,populateBean 實(shí)際上就是對(duì) bean 的依賴屬性進(jìn)行一個(gè)賦值填充,而 initializeBean 則是調(diào)用 Spring xml 中的 init 方法。
這個(gè)時(shí)候,我們看到這個(gè)初始化的過程,一般就應(yīng)該能猜到會(huì)發(fā)生 循環(huán)依賴? 的位置是哪一步了,而單從 bean 的初始化來看,循環(huán)依賴發(fā)生的位置就是在 createBeanInstance 實(shí)例化? 以及 populateBean 填充屬性 當(dāng)中,
發(fā)生的循環(huán)依賴也是
- 構(gòu)造器的循環(huán)依賴
- field屬性的循環(huán)依賴
那么 Spring 又是怎么解決這種單例的循環(huán)依賴的問題的呢?
?三級(jí)緩存
那么這三級(jí)緩存分別是哪三級(jí)的緩存呢?又分別代表了什么含義?
- singletonFactories :?jiǎn)卫龑?duì)象工廠的cache,用于存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用
- earlySingletonObjects :提前暴光的單例對(duì)象的Cache,存放原始的 bean 對(duì)象(尚未填充屬性),用于解決循環(huán)依賴
- singletonObjects:?jiǎn)卫龑?duì)象的cache,存放 bean 工廠對(duì)象,用于解決循環(huán)依賴
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一級(jí)緩存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二級(jí)緩存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三級(jí)緩存
如果要分析這個(gè) 三級(jí)緩存 如何解決循環(huán)依賴,那么勢(shì)必需要知道 Spring 中對(duì)象的創(chuàng)建的過程。
對(duì)象創(chuàng)建過程,可以大致分為五個(gè)步驟,
1.protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly)
AbstractBeanFactory? 中的 doGetBean()方法
2.protected Object getSingleton(String beanName, boolean allowEarlyReference)
DefaultSingletonBeanRegistry? 中的 getSingleton()方法
- 在這個(gè)方法中,先從一級(jí)緩存 singletonObjects 中去獲取。(如果獲取到就直接return)
- 如果獲取不到,并且對(duì)象正在創(chuàng)建中,就再從二級(jí)緩存 earlySingletonObjects 中獲取。
- 如果還是獲取不到且允許 singletonFactories? 通過 getObject()? 獲取,就從三級(jí)緩存singletonFactory.getObject()(三級(jí)緩存)獲取
- 如果獲取到了則:從 singletonFactories? 中移除,并放入 earlySingletonObjects 中
- 這就相當(dāng)于 ctrl+x ,把三級(jí)緩存中的數(shù)據(jù)剪切到了二級(jí)緩存。
源碼如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3.protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
AbstractAutowireCapableBeanFactory? 中的 doCreateBean() 方法
//添加到三級(jí)緩存
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
4.protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)
AbstractAutowireCapableBeanFactory? 中的 populateBean() 方法進(jìn)行屬性賦值
5.protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
AbstractAutowireCapableBeanFactory? 中的 initializeBean() 初始化對(duì)象
源碼部分阿粉就不再往上貼那么多了,大家找源碼肯定很簡(jiǎn)單,內(nèi)部也有具體方法的注釋,
Spring 解決循環(huán)依賴的訣竅就在于 singletonFactories 這個(gè)三級(jí)cache。
這個(gè) cache 的類型是 ObjectFactory?。這里就是解決循環(huán)依賴的關(guān)鍵,發(fā)生在createBeanInstance之后,也就是說單例對(duì)象此時(shí)已經(jīng)被創(chuàng)建出來(調(diào)用了構(gòu)造器)。
這個(gè)對(duì)象已經(jīng)被生產(chǎn)出來了,雖然還不完美(還沒有進(jìn)行初始化的第二步和第三步),但是已經(jīng)能被人認(rèn)出來了(根據(jù)對(duì)象引用能定位到堆中的對(duì)象),所以Spring此時(shí)將這個(gè)對(duì)象提前曝光出來讓大家認(rèn)識(shí),讓大家使用。
如果你能在面試的時(shí)候,回答成這個(gè)樣子,那么這個(gè)問題,你至少已經(jīng)算是回答的比較好了。
但是如果問到這里,面試官有意想要繼續(xù)深挖一下,你既然知道使用三級(jí)緩存解決了這個(gè)循環(huán)依賴的問題了,那么是不是必須三級(jí)緩存才能解決,二級(jí)緩存不能解決嗎?
這就另外又給你引申出一個(gè)問題來了,二級(jí)緩存到底能不能解決呢?
其實(shí),二級(jí)緩存也是能夠?qū)崿F(xiàn)的,如果自己想要實(shí)現(xiàn),那么就得去改寫 AbstractAutowireCapableBeanFactory? 的 doCreateBean 的方法了,
//添加到三級(jí)緩存
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
//從三級(jí)緩存中取出立刻放入二級(jí)緩存
getSingleton(beanName, true);
}
如果要使用二級(jí)緩存解決循環(huán)依賴,意味著Bean在構(gòu)造完后就創(chuàng)建代理對(duì)象,這樣違背了Spring設(shè)計(jì)原則。
Spring結(jié)合AOP跟Bean的生命周期,是在Bean創(chuàng)建完全之后通過AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來完成的,在這個(gè)后置處理的postProcessAfterInitialization方法中對(duì)初始化后的Bean完成AOP代理。
如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給Bean先創(chuàng)建代理,但是沒有出現(xiàn)循環(huán)依賴的情況下,設(shè)計(jì)之初就是讓Bean在生命周期的最后一步完成代理而不是在實(shí)例化后就立馬完成代理。
所以,你知道為什么不使用二級(jí)緩存直接來處理了,而是增加了三級(jí)緩存來處理這個(gè)循環(huán)依賴了吧!