最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

簡(jiǎn)單了解Spring循環(huán)依賴解決過程

 更新時(shí)間:2019年11月18日 09:51:57   作者:張?jiān)?jīng)  
這篇文章主要介紹了簡(jiǎn)單了解Spring循環(huán)依賴解決過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

這篇文章主要介紹了簡(jiǎn)單了解Spring循環(huán)依賴解決過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

前言

說起Spring中循環(huán)依賴的解決辦法,相信很多園友們都或多或少的知道一些,但當(dāng)真的要詳細(xì)說明的時(shí)候,可能又沒法一下將它講清楚。本文就試著盡自己所能,對(duì)此做出一個(gè)較詳細(xì)的解讀。另,需注意一點(diǎn),下文中會(huì)出現(xiàn)類的實(shí)例化跟類的初始化兩個(gè)短語(yǔ),為怕園友迷惑,事先聲明一下,本文的實(shí)例化是指剛執(zhí)行完構(gòu)造器將一個(gè)對(duì)象new出來(lái),但還未填充屬性值的狀態(tài),而初始化是指完成了屬性的依賴注入。

一、先說說Spring解決的循環(huán)依賴是什么

Java中的循環(huán)依賴分兩種,一種是構(gòu)造器的循環(huán)依賴,另一種是屬性的循環(huán)依賴。

構(gòu)造器的循環(huán)依賴就是在構(gòu)造器中有屬性循環(huán)依賴,如下所示的兩個(gè)類就屬于構(gòu)造器循環(huán)依賴:

@Service
public class Student {
  @Autowired
  private Teacher teacher;

  public Student (Teacher teacher) {
    System.out.println("Student init1:" + teacher);
  }

  public void learn () {
    System.out.println("Student learn");
  }
}
@Service
public class Teacher {
  @Autowired
  private Student student;

  public Teacher (Student student) {
    System.out.println("Teacher init1:" + student);

  }

  public void teach () {
    System.out.println("teach:");
    student.learn();
  }
}

這種循環(huán)依賴沒有什么解決辦法,因?yàn)镴VM虛擬機(jī)在對(duì)類進(jìn)行實(shí)例化的時(shí)候,需先實(shí)例化構(gòu)造器的參數(shù),而由于循環(huán)引用這個(gè)參數(shù)無(wú)法提前實(shí)例化,故只能拋出錯(cuò)誤。

Spring解決的循環(huán)依賴就是指屬性的循環(huán)依賴,如下所示:

@Service
public class Teacher {
  @Autowired
  private Student student;

  public Teacher () {
    System.out.println("Teacher init1:" + student);

  }

  public void teach () {
    System.out.println("teach:");
    student.learn();
  }
  
}
@Service
public class Student {
  @Autowired
  private Teacher teacher;

  public Student () {
    System.out.println("Student init:" + teacher);
  }

  public void learn () {
    System.out.println("Student learn");
  }
}

測(cè)試掃描類:

 @ComponentScan(value = "myPackage")
 public class ScanConfig {
   
 }

測(cè)試啟動(dòng)類:

public class SpringTest {

  public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);
    applicationContext.getBean(Teacher.class).teach();

  }
}

測(cè)試類執(zhí)行結(jié)果:

 Student init:null
 Teacher init:null
 teach:
 Student learn

可以看到,在構(gòu)造器執(zhí)行的時(shí)候未完成屬性的注入,而在調(diào)用方法的時(shí)候已經(jīng)完成了注入。下面就一起看看Spring內(nèi)部是在何時(shí)完成的屬性注入,又是如何解決的循環(huán)依賴。

二、循環(huán)依賴與屬性注入

1、對(duì)于非懶加載的類,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包掃描以及bean的初始化,下面就一起追蹤下去。

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // 其他代碼

    // Instantiate all remaining (non-lazy-init) singletons.
    beanFactory.preInstantiateSingletons();
  }

可以看到調(diào)用了beanFactory的一個(gè)方法,此處的beanFactory就是指我們最常見的那個(gè)DefaultListableBeanFactory,下面看它里面的這個(gè)方法。

2、DefaultListableBeanFactory的preInstantiateSingletons方法

public void preInstantiateSingletons() throws BeansException {
    
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // 判斷為非抽象類、是單例、非懶加載 才給初始化
        if (isFactoryBean(beanName)) {
          // 無(wú)關(guān)代碼(針對(duì)FactoryBean的處理)
        }
        else {
          // 重要?。?!普通bean就是在這里初始化的
          getBean(beanName);
        }
      }
    }

    // 其他無(wú)關(guān)代碼 
  }

可以看到,就是在此方法中循環(huán)Spring容器中所有的bean,依次對(duì)其進(jìn)行初始化,初始化的入口就是getBean方法

3、AbstractBeanFactory的getBean跟doGetBean方法

追蹤getBean方法:

 public Object getBean(String name) throws BeansException {
 return doGetBean(name, null, null, false);
 }

可見引用了重載的doGetBean方法,繼續(xù)追蹤之:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;
        
     // 方法1)從三個(gè)map中獲取單例類
    Object sharedInstance = getSingleton(beanName);
    // 省略無(wú)關(guān)代碼
    }
    else {
      // 如果是多例的循環(huán)引用,則直接報(bào)錯(cuò)
      if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
      }
      // 省略若干無(wú)關(guān)代碼
      try {
        // Create bean instance.
        if (mbd.isSingleton()) {
          // 方法2) 獲取單例對(duì)象
          sharedInstance = getSingleton(beanName, () -> {
            try { //方法3) 創(chuàng)建ObjectFactory中g(shù)etObject方法的返回值
              return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
              // Explicitly remove instance from singleton cache: It might have been put there
              // eagerly by the creation process, to allow for circular reference resolution.
              // Also remove any beans that received a temporary reference to the bean.
              destroySingleton(beanName);
              throw ex;
            }
          });
          bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
     }
    // 省略若干無(wú)關(guān)代碼
    return (T) bean;
  }

該方法比較長(zhǎng),對(duì)于解決循環(huán)引用來(lái)說,上面標(biāo)出來(lái)的3個(gè)方法起到了至關(guān)重要的作用,下面我們挨個(gè)攻克。

3.1) getSingleton(beanName)方法: 注意該方法跟方法2)是重載方法,名字一樣內(nèi)部邏輯卻大相徑庭。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);// 步驟A
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
        singletonObject = this.earlySingletonObjects.get(beanName);// 步驟B
        if (singletonObject == null && allowEarlyReference) {
          ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 步驟C
          if (singletonFactory != null) {
            singletonObject = singletonFactory.getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
          }
        }
      }
    }
    return singletonObject;
  }

通過上面的步驟可以看出這三個(gè)map的優(yōu)先級(jí)。其中singletonObjects里面存放的是初始化之后的單例對(duì)象;earlySingletonObjects中存放的是一個(gè)已完成實(shí)例化未完成初始化的早期單例對(duì)象;而singletonFactories中存放的是ObjectFactory對(duì)象,此對(duì)象的getObject方法返回值即剛完成實(shí)例化還未開始初始化的單例對(duì)象。所以先后順序是,單例對(duì)象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中。

當(dāng)debug到此處時(shí),以上述Teacher和Student兩個(gè)循環(huán)引用的類為例,如果第一個(gè)走到這一步的是Teacher,則從此處這三個(gè)map中g(shù)et到的值都是空,因?yàn)檫€未添加進(jìn)去。這個(gè)方法主要是給循環(huán)依賴中后來(lái)過來(lái)的對(duì)象用。

3.2)getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
        // 省略無(wú)關(guān)代碼
        beforeSingletonCreation(beanName); // 步驟A
        boolean newSingleton = false;
        // 省略無(wú)關(guān)代碼
        try {
          singletonObject = singletonFactory.getObject();// 步驟B
          newSingleton = true;
        }
        // 省略無(wú)關(guān)代碼
        finally {
          if (recordSuppressedExceptions) {
            this.suppressedExceptions = null;
          }
          afterSingletonCreation(beanName);// 步驟C
        }
        if (newSingleton) {
          addSingleton(beanName, singletonObject);// 步驟D
        }
      }
      return singletonObject;
    }
  }

獲取單例對(duì)象的主要邏輯就是此方法實(shí)現(xiàn)的,主要分為上面四個(gè)步驟,繼續(xù)挨個(gè)看:

步驟A:

protected void beforeSingletonCreation(String beanName) {
    // 判斷,并首次將beanName即teacher放入singletonsCurrentlyInCreation中
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
    }
  }

步驟C:

protected void afterSingletonCreation(String beanName) {
    // 得到單例對(duì)象后,再講beanName從singletonsCurrentlyInCreation中移除
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
  }

步驟D:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);//添加單例對(duì)象到map中
      this.singletonFactories.remove(beanName);//從早期暴露的工廠中移除,此map在解決循環(huán)依賴中發(fā)揮了關(guān)鍵的作用
      this.earlySingletonObjects.remove(beanName);//從早期暴露的對(duì)象map中移除
      this.registeredSingletons.add(beanName);//添加到已注冊(cè)的單例名字集合中
    }
  }

步驟B:

此處調(diào)用了ObjectFactory的getObject方法,此方法是在哪里實(shí)現(xiàn)的呢?返回的又是什么?且往回翻,找到3中的方法3,對(duì)java8函數(shù)式編程有過了解的園友應(yīng)該能看出來(lái),方法3 【createBean(beanName, mbd, args)】的返回值就是getObject方法的返回值,即方法3返回的就是我們需要的單例對(duì)象,下面且追蹤方法3而去。

3.3)AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[]) 方法

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

    // 省略無(wú)關(guān)代碼
    try {
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      return beanInstance;
    }
    // 省略無(wú)關(guān)代碼
  }

去掉無(wú)關(guān)代碼之后,關(guān)鍵方法只有doCreateBean方法,追蹤之:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

    BeanWrapper instanceWrapper = null;
    // 省略代碼
    if (instanceWrapper == null) {
      // 實(shí)例化bean
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
      // 重點(diǎn)?。。?shí)例化的對(duì)象添加到singletonFactories中 
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // 初始化bean
    Object exposedObject = bean;
    try {
      populateBean(beanName, mbd, instanceWrapper);//也很重要
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    // 省略無(wú)關(guān)代碼
    return exposedObject;
}

上面注釋中標(biāo)出的重點(diǎn)是此方法的關(guān)鍵。在addSingletonFactory方法中,將第二個(gè)參數(shù)ObjectFactory存入了singletonFactories供其他對(duì)象依賴時(shí)調(diào)用。然后下面的populateBean方法對(duì)剛實(shí)例化的bean進(jìn)行屬性注入(該方法關(guān)聯(lián)較多,本文暫時(shí)不展開追蹤了,有興趣的園友自行查看即可),如果遇到Spring中的對(duì)象屬性,則再通過getBean方法獲取該對(duì)象。至此,循環(huán)依賴在Spring中的處理過程已經(jīng)追溯完畢,下面我們總結(jié)一下。

小結(jié)

屬性注入主要是在populateBean方法中進(jìn)行的。對(duì)于循環(huán)依賴,以我們上文中的Teacher中注入了Student、Student中注入了Teacher為例來(lái)說明,假定Spring的加載順序?yàn)橄燃虞dTeacher,再加載Student。

getBean方法觸發(fā)Teacher的初始化后:

a. 首先走到3中的方法1),此時(shí)map中都為空,獲取不到實(shí)例;

b. 然后走到方法2)中,步驟A、步驟C、步驟D為控制map中數(shù)據(jù)的方法,實(shí)現(xiàn)簡(jiǎn)單,可暫不關(guān)注。其中步驟B的getObject方法觸發(fā)對(duì)方法3)的調(diào)用;

c. 在方法3)中,先通過createBeanInstance實(shí)例化Teacher對(duì)象,又將該實(shí)例化的對(duì)象通過addSingletonFactory方法放入singletonFactories中,完成Teacher對(duì)象早期的暴露;

d. 然后在方法3)中通過populateBean方法對(duì)Teacher對(duì)象進(jìn)行屬性的注入,發(fā)現(xiàn)它有一個(gè)Student屬性,則觸發(fā)getBean方法對(duì)Student進(jìn)行初始化

e. 重復(fù)a、b、c步驟,只是此時(shí)要初始化的是Student對(duì)象

f. 走到d的時(shí)候,調(diào)用populateBean對(duì)Student對(duì)象進(jìn)行屬性注入,發(fā)現(xiàn)它有一個(gè)Teacher屬性,則觸發(fā)getBean方法對(duì)Teacher進(jìn)行初始化;

g. 對(duì)Teacher進(jìn)行初始化,又來(lái)到a,但此時(shí)map已經(jīng)不為空了,因?yàn)橹霸赾步驟中已經(jīng)將Teacher實(shí)例放入了singletonFactories中,a中得到Teacher實(shí)例后返回;

h.完成f中對(duì)Student的初始化,繼而依次往上回溯完成Teacher的初始化;

完成Teacher的初始化后,Student的初始化就簡(jiǎn)單了,因?yàn)閙ap中已經(jīng)存了這個(gè)單例。

至此,Spring循環(huán)依賴的總結(jié)分析結(jié)束,一句話來(lái)概括一下:Spring通過將實(shí)例化后的對(duì)象提前暴露給Spring容器中的singletonFactories,解決了循環(huán)依賴的問題。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java Web 登錄頁(yè)面的實(shí)現(xiàn)代碼實(shí)例

    Java Web 登錄頁(yè)面的實(shí)現(xiàn)代碼實(shí)例

    這篇文章主要介紹了Java Web 登錄頁(yè)面的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • SpringBoot中獲取配置文件的注解詳解

    SpringBoot中獲取配置文件的注解詳解

    這篇文章主要介紹了SpringBoot中獲取配置文件的注解詳解, 一般情況下:如果我們只是在某個(gè)業(yè)務(wù)邏輯中需要獲取一下配置文件中的某項(xiàng)值,使用@Value,如果我們使用JavaBean來(lái)與配置文件進(jìn)行映射,我們直接使用@ConfigurationProperties,需要的朋友可以參考下
    2023-10-10
  • Spring中的@Conditional注解實(shí)現(xiàn)分析

    Spring中的@Conditional注解實(shí)現(xiàn)分析

    這篇文章主要介紹了Spring中的@Conditional注解實(shí)現(xiàn)分析,  @Conditional是Spring 4出現(xiàn)的注解,但是真正露出價(jià)值的是Spring Boot的擴(kuò)展@ConditionalOnBean等,需要的朋友可以參考下
    2023-12-12
  • 淺談java String.split丟失結(jié)尾空字符串的問題

    淺談java String.split丟失結(jié)尾空字符串的問題

    下面小編就為大家?guī)?lái)一篇淺談java String.split丟失結(jié)尾空字符串的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2017-02-02
  • java虛擬機(jī)中多線程總結(jié)

    java虛擬機(jī)中多線程總結(jié)

    在本篇內(nèi)容中小編給大家分享的是關(guān)于java虛擬機(jī)中多線程的知識(shí)點(diǎn)總結(jié)內(nèi)容,需要的朋友們參考學(xué)習(xí)下。
    2019-06-06
  • java 格式化時(shí)間的示例代碼

    java 格式化時(shí)間的示例代碼

    這篇文章主要介紹了java 格式化時(shí)間的示例代碼,幫助大家更好的利用Java處理時(shí)間,感興趣的朋友可以了解下
    2020-12-12
  • 基于Java實(shí)現(xiàn)文件和base64字符串轉(zhuǎn)換

    基于Java實(shí)現(xiàn)文件和base64字符串轉(zhuǎn)換

    這篇文章主要介紹了基于Java實(shí)現(xiàn)文件和base64字符串轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Java數(shù)據(jù)結(jié)構(gòu)之線段樹的原理與實(shí)現(xiàn)

    Java數(shù)據(jù)結(jié)構(gòu)之線段樹的原理與實(shí)現(xiàn)

    線段樹是一種二叉搜索樹,是用來(lái)維護(hù)區(qū)間信息的數(shù)據(jù)結(jié)構(gòu)。本文將利用示例詳細(xì)講講Java數(shù)據(jù)結(jié)構(gòu)中線段樹的原理與實(shí)現(xiàn),需要的可以參考一下
    2022-06-06
  • Java線程中的ThreadLocal類解讀

    Java線程中的ThreadLocal類解讀

    這篇文章主要介紹了Java線程中的ThreadLocal類解讀,ThreadLocal是一個(gè)泛型類,作用是實(shí)現(xiàn)線程隔離,ThreadLocal類型的變量,在每個(gè)線程中都會(huì)對(duì)應(yīng)一個(gè)具體對(duì)象,對(duì)象類型需要在聲明ThreadLocal變量時(shí)指定,需要的朋友可以參考下
    2023-11-11
  • SpringBoot3整合SpringCloud啟動(dòng)后nacos報(bào)錯(cuò)獲取不到配置、無(wú)法注冊(cè)服務(wù)的解決方案

    SpringBoot3整合SpringCloud啟動(dòng)后nacos報(bào)錯(cuò)獲取不到配置、無(wú)法注冊(cè)服務(wù)的解決方案

    文章介紹了如何使用Spring Boot 3.3.4和Spring Cloud 2023.0.3搭建微服務(wù)項(xiàng)目,并解決與Nacos服務(wù)注冊(cè)發(fā)現(xiàn)和配置中心的集成問題,主要解決了依賴版本不兼容、配置文件導(dǎo)入問題及服務(wù)注冊(cè)失敗等問題,感興趣的朋友跟隨小編一起看看吧
    2025-02-02

最新評(píng)論

建始县| 建阳市| 西安市| 镇远县| 五原县| 元阳县| 鹰潭市| 揭西县| 博兴县| 化州市| 微博| 临沂市| 乐业县| 平安县| 濮阳县| 锡林郭勒盟| 柳河县| 蛟河市| 介休市| 武川县| 崇文区| 泾源县| 天长市| 文水县| 新竹市| 盖州市| 长阳| 饶阳县| 通许县| 承德县| 黔东| 东源县| 化德县| 榕江县| 句容市| 南投县| 哈密市| 武穴市| 卢氏县| 大埔区| 五峰|