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

吐血給女朋友講解Spring循環(huán)依賴

開(kāi)發(fā) 前端
有的小伙伴內(nèi)心對(duì)于Spring的看法可能是,感覺(jué)自己懂了,但是讓自己來(lái)敘述給一個(gè)小白的話,可能部分人就講不出來(lái)了,這一篇呢,主要就是幫你解決這個(gè)痛點(diǎn),讓你徹底搞定Spring的循環(huán)依賴.

[[430219]]

前言

我們前面說(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)依賴

  1. @Component 
  2. public class AService { 
  3.     // AService中注入了BService 
  4.  @Autowired 
  5.  private BService bService; 
  6.  
  7. @Component 
  8. public class BService { 
  9.     // BService中也注入了AService 
  10.  @Autowired 
  11.  private AService aService; 

這是屬于比較常見(jiàn)的一種循環(huán)依賴,還有就是更多的之間的相互依賴,比如A依賴B,B依賴C,C依賴A,類似于三角戀...

當(dāng)然也有特殊的,自己的依賴自己

  1. // 自己依賴自己 
  2. @Component 
  3. public class AService { 
  4.     // A中注入了A 
  5.  @Autowired 
  6.  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)造器注入呢,先看代碼

  1. @Component 
  2. public class AService { 
  3. // @Autowired 
  4. // private BService bService; 
  5.  public AService(BService bService) { 
  6.  
  7.  } 
  8.  
  9.  
  10. @Component 
  11. public class BService { 
  12.  
  13. // @Autowired 
  14. // private AService aService; 
  15.  
  16.  public BService(AService aService){ 
  17.  
  18.  } 

上面這個(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ì)直接拋出異常

  1. 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賊船」

 

責(zé)任編輯:姜華 來(lái)源: Java賊船
相關(guān)推薦

2020-06-22 08:07:48

Spring依賴場(chǎng)景

2019-10-23 07:00:13

TCP三次握手四次揮手

2019-03-12 09:43:14

反向代理正向代理服務(wù)器

2019-04-09 09:40:23

2020-03-16 14:08:59

線程熔斷限流

2021-09-14 12:00:11

VR字節(jié)跳動(dòng)

2019-10-09 10:45:16

云計(jì)算Web互聯(lián)網(wǎng)

2021-06-22 07:45:57

React18startTransiReact

2020-10-19 13:01:31

刪庫(kù)程序員思科

2021-03-11 16:45:29

TCP程序C語(yǔ)言

2019-04-19 09:48:53

樂(lè)觀鎖悲觀鎖數(shù)據(jù)庫(kù)

2021-05-19 11:02:44

PythonTurtle參數(shù)

2019-07-22 10:34:31

大案牘術(shù)大數(shù)據(jù)Big Data

2019-04-26 14:46:18

GitGitHub局域網(wǎng)

2019-12-23 10:26:02

3PC分布式2PC

2021-06-22 07:30:07

React18Automatic b自動(dòng)批處理

2015-08-26 10:17:29

程序員女朋友

2023-05-04 08:06:27

Spring循環(huán)依賴

2021-03-09 12:27:05

微服務(wù) 微服務(wù)架構(gòu)應(yīng)用程序

2020-10-25 08:22:28

V8 引擎JavaScript回調(diào)函數(shù)
點(diǎn)贊
收藏

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