一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring如何解決循環(huán)依賴的問題

Spring如何解決循環(huán)依賴的問題

2020-08-26 10:04紀莫 Java教程

這篇文章主要介紹了Spring是如何解決循環(huán)依賴的問題,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

前言

在面試的時候這兩年有一個非常高頻的關于spring的問題,那就是spring是如何解決循環(huán)依賴的。這個問題聽著就是輕描淡寫的一句話,其實考察的內容還是非常多的,主要還是考察的應聘者有沒有研究過spring的源碼。但是說實話,spring的源碼其實非常復雜的,研究起來并不是個簡單的事情,所以我們此篇文章只是為了解釋清楚Spring是如何解決循環(huán)依賴的這個問題。

什么樣的依賴算是循環(huán)依賴?

用過Spring框架的人都對依賴注入這個詞不陌生,一個Java類A中存在一個屬性是類B的一個對象,那么我們就說類A的對象依賴類B,而在Spring中是依靠的IOC來實現(xiàn)的對象注入,也就是說創(chuàng)建對象的過程是IOC容器來實現(xiàn)的,并不需要自己在使用的時候通過new關鍵字來創(chuàng)建對象。
那么當類A中依賴類B的對象,而類B中又依賴類C的對象,最后類C中又依賴類A的對象的時候,這種情況最終的依賴關系會形成一個環(huán),這就是循環(huán)依賴。

Spring如何解決循環(huán)依賴的問題

循環(huán)依賴的類型

根據(jù)注入的時機可以分為兩種:

構造器循環(huán)依賴

依賴的對象是通過構造方法傳入的,在實例化bean的時候發(fā)生。

賦值屬性循環(huán)依賴

依賴的對象是通過setter方法傳入的,對象已經實例化,在屬性賦值和依賴注入的時候發(fā)生。
構造器循環(huán)依賴,本質上是無解的,實例化A的時候調用A的構造器,發(fā)現(xiàn)依賴了B,又去實例化B,然后調用B的構造器,發(fā)現(xiàn)又依賴的C,然后調用C的構造器去實例化,結果發(fā)起C的構造器里依賴了A,這就是個死循環(huán)無解。所以Spring也是不支持構造器循環(huán)依賴的,當發(fā)現(xiàn)存在構造器循環(huán)依賴時,會直接拋出BeanCurrentlyInCreationException 異常。
賦值屬性循環(huán)依賴,Spring只支持bean在單例模式下的循環(huán)依賴,其他模式下的循環(huán)依賴Spring也是會拋出BeanCurrentlyInCreationException 異常的。Spring通過對還在創(chuàng)建過程中的單例bean,進行緩存并提前暴露該單例,使得其他實例可以提前引用到該單例bean。

Spring為什么只支持單例模式下的bean的賦值情況下的循環(huán)依賴

在prototype的模式下的bean,使用了一個ThreadLocal變量prototypesCurrentlyInCreation來記錄當前線程正在創(chuàng)建中的bean,這個變量在AbtractBeanFactory類里。在創(chuàng)建前用beanName記錄bean,在創(chuàng)建完成后刪除bean。在prototypesCurrentlyInCreation里采用了一個Set對象來存儲正在創(chuàng)建中的bean。我們都知道Set是不允許存在重復對象的,這樣就能保證同一個bean在一個線程中只能有一個正在創(chuàng)建。
下面是prototypesCurrentlyInCreation變量在刪除bean時的操作,在AbtractBeanFactorybeforePrototypeCreation操作里。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
protected void afterPrototypeCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  if (curVal instanceof String) {
   this.prototypesCurrentlyInCreation.remove();
  }
  else if (curVal instanceof Set) {
   Set<String> beanNameSet = (Set<String>) curVal;
   beanNameSet.remove(beanName);
   if (beanNameSet.isEmpty()) {
    this.prototypesCurrentlyInCreation.remove();
   }
  }
 }

從上面的代碼中看出,當變量為一個的時候采用了一個String對象來存儲,節(jié)省了一些內存空間。
AbstractBeanFactory類的doGetBean方法里先判斷是否為單例對象,不是單例對象,則直接判斷當前線程是否已經存在了正在創(chuàng)建的bean。存在的話直接拋出異常。Spring如何解決循環(huán)依賴的問題

這個isPrototypeCurrentlyInCreation()方法的實現(xiàn)代碼如下:

?
1
2
3
4
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  return curVal != null && (curVal.equals(beanName) || curVal instanceof Set && ((Set)curVal).contains(beanName));
 }

因為有了這個機制,spring在原型模式下是解決不了bean的循環(huán)依賴的,當發(fā)現(xiàn)有循環(huán)依賴的時候會直接拋出BeanCurrentlyInCreationException異常的。

那么為什么spring在單例模式下的構造賦值也不支持循環(huán)依賴呢?

其實原理和原型模式下的情況類似,在單例模式下,bean也會用一個Set集合來保存正在創(chuàng)建中的bean,在創(chuàng)建前保存,創(chuàng)建完成后刪除。
這個對象在DefaultSingletonBeanRegistry類下變量名為:singletonsCurrentlyInCreation

?
1
2
3
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
     private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
}

判定代碼在DefaultSingletonBeanRegistry類的beforeSingletonCreation方法下。

?
1
2
3
4
5
protected void beforeSingletonCreation(String beanName) {
 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
  throw new BeanCurrentlyInCreationException(beanName);
 }
}

在上面這個方法中,判定singletonsCurrentlyInCreation是否能成功的保存一個單例bean。如果不能成功保存,那么就會直接拋出BeanCurrentlyInCreationException異常。

單例模式下的Setter賦值循環(huán)依賴

終于到了我們的重點,Spring是如何解決單例模式下的Setter賦值的循環(huán)依賴了。
其實主要的就是靠提前暴露創(chuàng)建中的單例實例。
那么具體是一個怎樣的過程呢?
例如:上面那個圖的例子,A依賴B,B依賴C,C又依賴B。
過程如下:

  • 創(chuàng)建A,調用構造方法,完成構造,進行屬性賦值注入,發(fā)現(xiàn)依賴B,去實例化B
  • 創(chuàng)建B,調用構造方法,完成構造,進行屬性賦值注入,發(fā)現(xiàn)依賴C,去實例化C

創(chuàng)建C,調用構造方法,完成構造,進行屬性賦值注入,發(fā)現(xiàn)依賴A。
這個時候就是解決循環(huán)依賴的關鍵了,因為A已經通過構造方法已經構造完成了,也就是說已經將Bean的在堆中分配好了內存,這樣即使A再填充屬性值也不會更改內存地址了,所以此時可以提前拿出來A的引用,來完成C的實例化。

這樣上面創(chuàng)建C過程就會變成了:

  • 創(chuàng)建C,調用構造方法,完成構造,進行屬性賦值注入,發(fā)現(xiàn)依賴A,A已經構造完成,直接引用,完成C的實例化
  • C完成實例化后,注入B,B也完成了實例化,然后B注入A,A也完成了實例化

為了能獲取到創(chuàng)建中單例bean,spring提供了三級緩存來將正在創(chuàng)建中的bean提前暴露。
在類DefaultSingletonBeanRegistry下,即下圖紅框中的三個Map對象。

Spring如何解決循環(huán)依賴的問題

這三個緩存Map的作用如下:

  • 一級緩存,singletonObjects 單例緩存,存儲已經實例化的單例bean。
  • 二級緩存,earlySingletonObjects 提前暴露的單例緩存,這里存儲的bean是剛剛構造完成,但還會通過屬性注入bean。
  • 三級緩存,singletonFactories 生產單例的工廠緩存,存儲工廠。

首先在創(chuàng)建bean的時候會先創(chuàng)建一個和bean同名的單例工廠,并將bean先放入到單例工廠中。代碼在AbstractAutowireCapableBeanFactory類的doCreateBean方法中。

?
1
2
3
4
5
6
7
8
9
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    ......
    this.addSingletonFactory(beanName, new ObjectFactory<Object>() {
  public Object getObject() throws BeansException {
   return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
  }
 });
    .....
}

而上面的代碼中的addSingletonFactory方法的代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
 Assert.notNull(singletonFactory, "Singleton factory must not be null");
 Map var3 = this.singletonObjects;
 synchronized(this.singletonObjects) {
  if (!this.singletonObjects.containsKey(beanName)) {
   this.singletonFactories.put(beanName, singletonFactory);
   this.earlySingletonObjects.remove(beanName);
   this.registeredSingletons.add(beanName);
  }
 
 }
}

addSingletonFactory方法的作用通過代碼就可以看到是將存在了正在創(chuàng)建中的bean的單例工廠,放在三級緩存里,這樣保證了在循環(huán)依賴查找的時候是可以找到bean的引用的。
具體讀取緩存獲取bean的過程在類DefaultSingletonBeanRegistrygetSingleton方法里。
如下源碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
   Map var4 = this.singletonObjects;
   synchronized(this.singletonObjects) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null && allowEarlyReference) {
     ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
     if (singletonFactory != null) {
      singletonObject = singletonFactory.getObject();
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
     }
    }
   }
  }
 
  return singletonObject != NULL_OBJECT ? singletonObject : null;
 }

通過上面的源碼我們可以看到,在獲取單例Bean的時候,會先從一級緩存singletonObjects里獲取,如果沒有獲取到(說明不存在或沒有實例化完成),會去第二級緩存earlySingletonObjects中去找,如果還是沒有找到的話,就會三級緩存中獲取單例工廠singletonFactory,通過從singletonFactory中獲取正在創(chuàng)建中的引用,將singletonFactory存儲在earlySingletonObjects 二級緩存中,這樣就將創(chuàng)建中的單例引用從三級緩存中升級到了二級緩存中,二級緩存earlySingletonObjects,是會提前暴露已完成構造,還可以執(zhí)行屬性注入的單例bean的。
這個時候如何還有其他的bean也是需要屬性注入,那么就可以直接從earlySingletonObjects中獲取了。

上面的例子中的過程中的A,在注入C的時候,其實并沒有真正的初始化完成,等到順利的注入了B才算是真正的初始化完成。
整個過程如下圖:

Spring如何解決循環(huán)依賴的問題

總結

到此這篇關于Spring如何解決循環(huán)依賴的問題的文章就介紹到這了,更多相關Spring解決循環(huán)依賴內容請搜索服務器之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/jimoer/p/13562707.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: yy8090韩国日本三理论免费 | 17岁俄罗斯csgo | chinesefree普通对话 | 亚洲精品一区二区三区在线观看 | 国产精品久久久久无毒 | 国产日产欧产精品精品软件 | 日本三级香港三级久久99 | 明星ai人脸替换脸忘忧草 | 日本福利片国产午夜久久 | 美女在线看永久免费网址 | 私人影院在线播放 | 香蕉eeww99国产精品 | 视频高h| 亚洲一级特黄 | 欧美专区在线观看 | 全彩调教侵犯h本子全彩妖气he | 嫩草成人国产精品 | 九九99亚洲精品久久久久 | 亚洲男人第一天堂 | 亚洲福利二区 | 射逼网| brazzersxxx欧美 | 毛片影院 | 欧美1| 极品丝袜老师h系列全文阅读 | www亚洲视频| 四虎最新紧急更新地址 | 国产在视频线在精品 | 欧美午夜视频一区二区三区 | 暖暖 免费 高清 日本 在线1 | 91热这里只有精品 | 欧美一级专区免费大片 | 午夜AV亚洲一码二中文字幕青青 | chinese帅男gayvideo| 国产三区二区 | 嫩草影院久久99 | 亚洲色欲色欲综合网站 | 狠狠燥| 四虎免费影院ww4164h | 欧美成人第一页 | chinesespank打屁股 |