自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring的循環(huán)依賴,到底是什么樣的

開發(fā) 前端
什么是循環(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)。

前一段時(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)依賴了吧!


責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2009-03-25 09:45:15

美國軟件公司工作環(huán)境

2013-05-16 10:00:11

2010-06-07 13:47:39

培訓(xùn)

2023-11-28 08:00:00

SpringJava

2020-11-06 17:49:38

程序員技術(shù)開發(fā)

2022-02-28 10:16:12

算力網(wǎng)絡(luò)新基建東數(shù)西算

2020-03-05 10:28:19

MySQLMRR磁盤讀

2022-10-08 00:00:00

Spring數(shù)據(jù)庫項(xiàng)目

2009-10-26 13:36:10

BSM

2009-06-09 22:11:44

JavaScriptObject

2023-10-11 08:29:54

volatileJava原子性

2023-01-06 21:03:59

2013-08-29 11:38:53

企業(yè)App

2022-10-30 15:03:25

人工智能倉庫管理機(jī)器人

2011-04-27 09:30:48

企業(yè)架構(gòu)

2020-10-14 06:22:14

UWB技術(shù)感知

2010-11-01 01:25:36

Windows NT

2020-09-22 08:22:28

快充

2020-09-27 06:53:57

MavenCDNwrapper

2013-01-31 11:51:37

開源KVM
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)