吐血給女朋友講解Spring循環(huán)依賴
前言
我們前面說(shuō)了幾遍Spring的文章,了解了比較核心的知識(shí)點(diǎn)IOC和AOP,還有就是事務(wù)傳播這種,不知道大家聽(tīng)過(guò)Spring的循環(huán)依賴這個(gè)問(wèn)題嗎,而且這個(gè)問(wèn)題是面試經(jīng)常問(wèn)的,屬于Spring的一個(gè)比較重要的話題,也比較典型,比較考驗(yàn)一個(gè)人對(duì)Spring的研究程度,也算是Spring的一個(gè)高階問(wèn)題之一了
有的小伙伴內(nèi)心對(duì)于Spring的看法可能是,感覺(jué)自己懂了,但是讓自己來(lái)敘述給一個(gè)小白的話,可能部分人就講不出來(lái)了,這一篇呢,主要就是幫你解決這個(gè)痛點(diǎn),讓你徹底搞定Spring的循環(huán)依賴,也讓你下次不再擔(dān)心,而是張口就來(lái)
面試前看一看,offer輕松拿一拿,你還不關(guān)注等啥呢,月薪50K的薪資等著你呢,到時(shí)候如果你糾結(jié)選擇哪個(gè)offer,可以來(lái)把你的喜訊分享給我的嘞
循環(huán)依賴問(wèn)題,本文會(huì)通過(guò)三個(gè)方面來(lái)簡(jiǎn)單介紹
1、什么是Spring的循環(huán)依賴
2、多種情況下的循環(huán)依賴
3、Spring如何解決循環(huán)依賴
了解Spring循環(huán)依賴
什么是循環(huán)依賴
- @Component
- public class AService {
- // AService中注入了BService
- @Autowired
- private BService bService;
- }
- @Component
- public class BService {
- // BService中也注入了AService
- @Autowired
- private AService aService;
- }
這是屬于比較常見(jiàn)的一種循環(huán)依賴,還有就是更多的之間的相互依賴,比如A依賴B,B依賴C,C依賴A,類似于三角戀...
當(dāng)然也有特殊的,自己的依賴自己
- // 自己依賴自己
- @Component
- public class AService {
- // A中注入了A
- @Autowired
- private AService aService;
- }
關(guān)于上面service的Spring bean的創(chuàng)建,其實(shí)本質(zhì)上也會(huì)一個(gè)對(duì)象的創(chuàng)建,既然是對(duì)象,就要明白一個(gè)對(duì)象需要完成的對(duì)象包含兩個(gè)部分:當(dāng)前對(duì)象的實(shí)例化和對(duì)象屬性的實(shí)例化
我們?nèi)绻靡粋€(gè)常人的思維去考慮,這肯定是做不到的啊,A需要B,B需要A,這不成死循環(huán)了,和死鎖一個(gè)道理了,這就很尷尬了,該怎么解決呢,接著看下去
多種情況下的循環(huán)依賴
Spring的循環(huán)依賴也是可能出現(xiàn)多種情況的,比如構(gòu)造器注入,setter注入等等,那什么情況下Spring可以解決循環(huán)依賴,什么情況下又不能解決呢
Spring解決循環(huán)依賴的前提條件就是:
1、出現(xiàn)循環(huán)依賴的bean必須是單例的
2、依賴注入的方式不能全是構(gòu)造器注入
注意不能全是這幾個(gè)字眼,這里需要強(qiáng)調(diào)一點(diǎn)的是,大家可能會(huì)看到很多關(guān)于Spring解決循環(huán)依賴的博客,其中只能解決setter注入的方式這種說(shuō)法是錯(cuò)誤的,只要不全是構(gòu)造器注入Spring就可以解決
Spring bean的創(chuàng)建,其實(shí)本質(zhì)上也會(huì)一個(gè)對(duì)象的創(chuàng)建,既然是對(duì)象,就要明白一個(gè)對(duì)象需要完成的對(duì)象包含兩個(gè)部分:當(dāng)前對(duì)象的實(shí)例化和對(duì)象屬性的實(shí)例化,在Spring中,對(duì)象的實(shí)例化是通過(guò)反射實(shí)現(xiàn)的,而對(duì)象的屬性則是在對(duì)象實(shí)例化之后通過(guò)一定的方法設(shè)置的,知道了這一點(diǎn),可以更好的幫助大家去理解
上面說(shuō)的第一點(diǎn)必須是單例的其實(shí)很好理解,你想啊,如果是多個(gè)實(shí)例,該引入哪個(gè)實(shí)例就不知道了,Spring框架就蒙圈了,大膽猜測(cè)一下,Spring以后可能會(huì)嘗試解決這個(gè)問(wèn)題,大概率是通過(guò)配置的方式來(lái)告訴該注入哪個(gè)
但是第二點(diǎn)呢,不能全是構(gòu)造器注入呢,先看代碼
- @Component
- public class AService {
- // @Autowired
- // private BService bService;
- public AService(BService bService) {
- }
- }
- @Component
- public class BService {
- // @Autowired
- // private AService aService;
- public BService(AService aService){
- }
- }
上面這個(gè)例子大家看得懂吧,別告訴我你看不懂,AService中的BService注入是通過(guò)構(gòu)造器,反之也是通過(guò)構(gòu)造器注入,這個(gè)時(shí)候Spring是無(wú)法解決循環(huán)依賴問(wèn)題的,如果項(xiàng)目中出現(xiàn)兩個(gè)這樣的引用,在啟動(dòng)的時(shí)候就會(huì)直接拋出異常
- Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
解決循環(huán)依賴
首先呢,Spring解決循環(huán)依賴依靠的就是內(nèi)部維護(hù)的三個(gè)Map,也就是咱們常說(shuō)的三級(jí)緩存,不知道大家聽(tīng)過(guò)沒(méi)有
之所以被叫做三級(jí)緩存,大概是因?yàn)樽⑨屔隙际怯肅ache,而起的作用也類似一個(gè)緩存的作用
1、singletonObjects:這個(gè)是單例的容器池,緩存創(chuàng)建完成單例Bean的地方
2、singletonFactories:用來(lái)映射創(chuàng)建Bean的原始工廠
3、earlySingletonObjects:用來(lái)映射Bean的早起引用的,也就是說(shuō)在這個(gè)Map中的Bean不是完整的,屬于半成品,甚至還不能被稱為Bean,只是一個(gè)Instance
后面的兩個(gè)Map其實(shí)屬于是過(guò)程中的消耗品,什么意思呢,就是創(chuàng)建Bean的時(shí)候,需要暫時(shí)存儲(chǔ)在這里,過(guò)后完成之后就清除掉了
我們先來(lái)看一下這個(gè)創(chuàng)建過(guò)程,我把這個(gè)大致分了四個(gè)步驟,看著也比較清晰,大家也比較好理解:
1、A找B找不到:
創(chuàng)建AService的過(guò)程中發(fā)現(xiàn)需要BService,于是AService將去尋找BService,發(fā)現(xiàn)找不到,尋找的路徑是一級(jí)緩存、二級(jí)緩存、三級(jí)緩存,于是AService把自己放到了三級(jí)緩存中
2、B找A找到了:
實(shí)例化BService,實(shí)例化過(guò)程中發(fā)現(xiàn)需要AService,于是也是按照上述路徑去尋找,在三級(jí)緩存中找到了AService
3、B創(chuàng)建完成:
然后就把三級(jí)緩存中的AService拿出來(lái),放到了二級(jí)緩存中,并刪除三級(jí)緩存中的AService,此時(shí)BService可以成功引用,順利初始化完畢,把自己放到了一級(jí)緩存中了(而此時(shí)BService中的AService依然是創(chuàng)建中的狀態(tài)
4、A創(chuàng)建完成:
繼續(xù)完善AService,此時(shí)再去尋找BService,拿出來(lái)直接引用就好了,把自己放入到一級(jí)緩存中,刪除二級(jí)緩存,刪除在創(chuàng)建的狀態(tài)信息
問(wèn)題的本質(zhì)和two sum的本質(zhì)有些類似,這個(gè)是leetcode上序號(hào)為1 的題目,問(wèn)題的內(nèi)容是:
給定nums = [2,7,11,15] , target = 9,那么要返回[0,1] ,因?yàn)? + 7 = 9
這道題的解題思路就是通過(guò)Map,先去Map中找到需要的數(shù)字,沒(méi)有就把當(dāng)前數(shù)字放進(jìn)去,有的話就直接拿到并一起返回
和三級(jí)緩存的本質(zhì)類似,先去緩存中找到需要的Bean,找到了萬(wàn)事大吉,直接創(chuàng)建完成返回,找不到就把自己放進(jìn)去
來(lái)一起簡(jiǎn)單的分析一波源碼,不看太多,不用發(fā)愁
單例模式下,第一次獲取Bean時(shí)由于Bean示例還未實(shí)例化,因此會(huì)先創(chuàng)建Bean然后放入緩存中,以后再次調(diào)用獲取Bean方法將直接從緩存中獲取,不再重新創(chuàng)建。
緩存添加過(guò)程主要發(fā)生在創(chuàng)建Bean也就是doCreateBean()過(guò)程中。
從代碼中可以看到在這里將beanName放入三級(jí)緩存中,并且從二級(jí)緩存中移除。這里的addSingletonFactory發(fā)生在createBeanInstance之后,此時(shí)已經(jīng)有了實(shí)例對(duì)象,但是還沒(méi)創(chuàng)建完成便放入三級(jí)緩存當(dāng)中。
在doCreateBean()方法執(zhí)行完成之后,Bean實(shí)例創(chuàng)建完成,因此在第二個(gè)getSingleton()中的finally塊中如果是新的單例對(duì)象則會(huì)調(diào)用addSingleton()方法
可以看到此時(shí)將加入一級(jí)緩存中,并且從二級(jí)、三級(jí)緩存中移除。
緩存的獲取是通過(guò)getSingleton(String beanName)方法獲取的,其源碼如下:
在代碼中,首先從一級(jí)緩存singletonObjects中獲取Bean;如果獲取不到并且獲取的Bean被標(biāo)記為正在創(chuàng)建中,則從二級(jí)緩存earlySingletonObjects中獲取
如果二級(jí)緩存中依然獲取不到Bean,并且允許從三級(jí)換從中獲取Bean,則從三級(jí)緩存中獲取Bean,此時(shí)如果獲取到了Bean,則將該Bean從三級(jí)緩存中移除,然后添加進(jìn)二級(jí)緩存(緩存升級(jí)),否則返回null。
最后問(wèn)一個(gè)靈魂的問(wèn)題,為什么不用二級(jí)緩存,而用三級(jí)緩存呢?
如果創(chuàng)建的Bean有對(duì)應(yīng)的代理,那其他對(duì)象注入時(shí),注入的應(yīng)該是對(duì)應(yīng)的代理對(duì)象;但是Spring無(wú)法提前知道這個(gè)對(duì)象是不是有循環(huán)依賴的情況,而正常情況下(沒(méi)有循環(huán)依賴情況),Spring都是在創(chuàng)建好完成品Bean之后才創(chuàng)建對(duì)應(yīng)的代理。這時(shí)候Spring有兩個(gè)選擇:
不管有沒(méi)有循環(huán)依賴,都提前創(chuàng)建好代理對(duì)象,并將代理對(duì)象放入緩存,出現(xiàn)循環(huán)依賴時(shí),其他對(duì)象直接就可以取到代理對(duì)象并注入。
不提前創(chuàng)建好代理對(duì)象,在出現(xiàn)循環(huán)依賴被其他對(duì)象注入時(shí),才實(shí)時(shí)生成代理對(duì)象。這樣在沒(méi)有循環(huán)依賴的情況下,Bean就可以按著Spring設(shè)計(jì)原則的步驟來(lái)創(chuàng)建。
Spring選擇了第二種方式,那怎么做到提前曝光對(duì)象而又不生成代理呢?
Spring就是在對(duì)象外面包一層ObjectFactory,提前曝光的是ObjectFactory對(duì)象,在被注入時(shí)才在ObjectFactory.getObject方式內(nèi)實(shí)時(shí)生成代理對(duì)象,并將生成好的代理對(duì)象放入到第二級(jí)緩存Map
本文轉(zhuǎn)載自微信公眾號(hào)「Java賊船」