欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談Spring 解決循環(huán)依賴必須要三級(jí)緩存嗎

 更新時(shí)間:2020年10月15日 09:53:40   作者:奮斗的小皇帝  
這篇文章主要介紹了淺談Spring 解決循環(huán)依賴必須要三級(jí)緩存嗎,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

我們都知道 Spring 是通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴的,但是解決循環(huán)依賴真的需要使用到三級(jí)緩沖嗎?只使用兩級(jí)緩存是否可以呢?本篇文章就 Spring 是如何使用三級(jí)緩存解決循環(huán)依賴作為引子,驗(yàn)證兩級(jí)緩存是否可以解決循環(huán)依賴。

循環(huán)依賴

既然要解決循環(huán)依賴,那么就要知道循環(huán)依賴是什么。如下圖所示:

通過(guò)上圖,我們可以看出:

  • A 依賴于 B
  • B 依賴于 C
  • C 依賴于 A
public class A {
  private B b;
}

public class B {
  private C c;
}

public class C {
  private A a;
}

這種依賴關(guān)系形成了一種閉環(huán),從而造成了循環(huán)依賴的局面。

下面是未解決循環(huán)依賴的常規(guī)步驟:

  • 實(shí)例化 A,此時(shí) A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行。
  • A 對(duì)象發(fā)現(xiàn)需要注入 B 對(duì)象,但是容器中并沒有 B 對(duì)象(如果對(duì)象創(chuàng)建完成并且屬性注入完成和執(zhí)行完初始化方法就會(huì)放入容器中)。
  • 實(shí)例化 B,此時(shí) B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行。
  • B 對(duì)象發(fā)現(xiàn)需要注入 C 對(duì)象,但是容器中并沒有 C 對(duì)象。
  • 實(shí)例化 C,此時(shí) C 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行。
  • C 對(duì)象發(fā)現(xiàn)需要注入 A 對(duì)象,但是容器中并沒有 A 對(duì)象。
  • 重復(fù)步驟 1。

三級(jí)緩存

Spring 解決循環(huán)依賴的核心就是提前暴露對(duì)象,而提前暴露的對(duì)象就是放置于第二級(jí)緩存中。下表是三級(jí)緩存的說(shuō)明:

名稱 描述
singletonObjects 一級(jí)緩存,存放完整的 Bean。
earlySingletonObjects 二級(jí)緩存,存放提前暴露的Bean,Bean 是不完整的,未完成屬性注入和執(zhí)行 init 方法。
singletonFactories 三級(jí)緩存,存放的是 Bean 工廠,主要是生產(chǎn) Bean,存放到二級(jí)緩存中。

所有被 Spring 管理的 Bean,最終都會(huì)存放在 singletonObjects 中,這里面存放的 Bean 是經(jīng)歷了所有生命周期的(除了銷毀的生命周期),完整的,可以給用戶使用的。

earlySingletonObjects 存放的是已經(jīng)被實(shí)例化,但是還沒有注入屬性和執(zhí)行 init 方法的 Bean。

singletonFactories 存放的是生產(chǎn) Bean 的工廠。

Bean 都已經(jīng)實(shí)例化了,為什么還需要一個(gè)生產(chǎn) Bean 的工廠呢?這里實(shí)際上是跟 AOP 有關(guān),如果項(xiàng)目中不需要為 Bean 進(jìn)行代理,那么這個(gè) Bean 工廠就會(huì)直接返回一開始實(shí)例化的對(duì)象,如果需要使用 AOP 進(jìn)行代理,那么這個(gè)工廠就會(huì)發(fā)揮重要的作用了,這也是本文需要重點(diǎn)關(guān)注的問題之一。

解決循環(huán)依賴

Spring 是如何通過(guò)上面介紹的三級(jí)緩存來(lái)解決循環(huán)依賴的呢?這里只用 A,B 形成的循環(huán)依賴來(lái)舉例:

  • 實(shí)例化 A,此時(shí) A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,A 只是一個(gè)半成品。
  • 為 A 創(chuàng)建一個(gè) Bean 工廠,并放入到 singletonFactories 中。
  • 發(fā)現(xiàn) A 需要注入 B 對(duì)象,但是一級(jí)、二級(jí)、三級(jí)緩存均為發(fā)現(xiàn)對(duì)象 B。
  • 實(shí)例化 B,此時(shí) B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,B 只是一個(gè)半成品。
  • 為 B 創(chuàng)建一個(gè) Bean 工廠,并放入到 singletonFactories 中。
  • 發(fā)現(xiàn) B 需要注入 A 對(duì)象,此時(shí)在一級(jí)、二級(jí)未發(fā)現(xiàn)對(duì)象 A,但是在三級(jí)緩存中發(fā)現(xiàn)了對(duì)象 A,從三級(jí)緩存中得到對(duì)象 A,并將對(duì)象 A 放入二級(jí)緩存中,同時(shí)刪除三級(jí)緩存中的對(duì)象 A。(注意,此時(shí)的 A 還是一個(gè)半成品,并沒有完成屬性填充和執(zhí)行初始化方法)
  • 將對(duì)象 A 注入到對(duì)象 B 中。
  • 對(duì)象 B 完成屬性填充,執(zhí)行初始化方法,并放入到一級(jí)緩存中,同時(shí)刪除二級(jí)緩存中的對(duì)象 B。(此時(shí)對(duì)象 B 已經(jīng)是一個(gè)成品)
  • 對(duì)象 A 得到對(duì)象 B,將對(duì)象 B 注入到對(duì)象 A 中。(對(duì)象 A 得到的是一個(gè)完整的對(duì)象 B)
  • 對(duì)象 A 完成屬性填充,執(zhí)行初始化方法,并放入到一級(jí)緩存中,同時(shí)刪除二級(jí)緩存中的對(duì)象 A。

我們從源碼中來(lái)分析整個(gè)過(guò)程:

創(chuàng)建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
  BeanWrapper instanceWrapper = null;
  
  if (instanceWrapper == null) {
    // ① 實(shí)例化對(duì)象
    instanceWrapper = this.createBeanInstance(beanName, mbd, args);
  }

  final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
  Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
  
  // ② 判斷是否允許提前暴露對(duì)象,如果允許,則直接添加一個(gè) ObjectFactory 到三級(jí)緩存
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 添加三級(jí)緩存的方法詳情在下方
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  // ③ 填充屬性
  this.populateBean(beanName, mbd, instanceWrapper);
  // ④ 執(zhí)行初始化方法,并創(chuàng)建代理
  exposedObject = initializeBean(beanName, exposedObject, mbd);
  
  return exposedObject;
}

添加三級(jí)緩存的方法如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(singletonFactory, "Singleton factory must not be null");
  synchronized (this.singletonObjects) {
    if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級(jí)緩存中不存在此對(duì)象
      this.singletonFactories.put(beanName, singletonFactory); // 添加至三級(jí)緩存
      this.earlySingletonObjects.remove(beanName); // 確保二級(jí)緩存沒有此對(duì)象
      this.registeredSingletons.add(beanName);
    }
  }
}

@FunctionalInterface
public interface ObjectFactory<T> {
  T getObject() throws BeansException;
}

通過(guò)這段代碼,我們可以知道 Spring 在實(shí)例化對(duì)象的之后,就會(huì)為其創(chuàng)建一個(gè) Bean 工廠,并將此工廠加入到三級(jí)緩存中。

因此,Spring 一開始提前暴露的并不是實(shí)例化的 Bean,而是將 Bean 包裝起來(lái)的 ObjectFactory。為什么要這么做呢?

這實(shí)際上涉及到 AOP,如果創(chuàng)建的 Bean 是有代理的,那么注入的就應(yīng)該是代理 Bean,而不是原始的 Bean。但是 Spring 一開始并不知道 Bean 是否會(huì)有循環(huán)依賴,通常情況下(沒有循環(huán)依賴的情況下),Spring 都會(huì)在完成填充屬性,并且執(zhí)行完初始化方法之后再為其創(chuàng)建代理。但是,如果出現(xiàn)了循環(huán)依賴的話,Spring 就不得不為其提前創(chuàng)建代理對(duì)象,否則注入的就是一個(gè)原始對(duì)象,而不是代理對(duì)象。因此,這里就涉及到應(yīng)該在哪里提前創(chuàng)建代理對(duì)象?

Spring 的做法就是在 ObjectFactory 中去提前創(chuàng)建代理對(duì)象。它會(huì)執(zhí)行 getObject() 方法來(lái)獲取到 Bean。實(shí)際上,它真正執(zhí)行的方法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  Object exposedObject = bean;
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
        SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
        // 如果需要代理,這里會(huì)返回代理對(duì)象;否則返回原始對(duì)象
        exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
      }
    }
  }
  return exposedObject;
}

因?yàn)樘崆斑M(jìn)行了代理,避免對(duì)后面重復(fù)創(chuàng)建代理對(duì)象,會(huì)在 earlyProxyReferences 中記錄已被代理的對(duì)象。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
    implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
  @Override
  public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 記錄已被代理的對(duì)象
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
  }
}

通過(guò)上面的解析,我們可以知道 Spring 需要三級(jí)緩存的目的是為了在沒有循環(huán)依賴的情況下,延遲代理對(duì)象的創(chuàng)建,使 Bean 的創(chuàng)建符合 Spring 的設(shè)計(jì)原則。

如何獲取依賴

我們目前已經(jīng)知道了 Spring 的三級(jí)依賴的作用,但是 Spring 在注入屬性的時(shí)候是如何去獲取依賴的呢?

他是通過(guò)一個(gè) getSingleton() 方法去獲取所需要的 Bean 的。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 一級(jí)緩存
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      // 二級(jí)緩存
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 三級(jí)緩存
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // Bean 工廠中獲取 Bean
          singletonObject = singletonFactory.getObject();
          // 放入到二級(jí)緩存中
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

當(dāng) Spring 為某個(gè) Bean 填充屬性的時(shí)候,它首先會(huì)尋找需要注入對(duì)象的名稱,然后依次執(zhí)行 getSingleton() 方法得到所需注入的對(duì)象,而獲取對(duì)象的過(guò)程就是先從一級(jí)緩存中獲取,一級(jí)緩存中沒有就從二級(jí)緩存中獲取,二級(jí)緩存中沒有就從三級(jí)緩存中獲取,如果三級(jí)緩存中也沒有,那么就會(huì)去執(zhí)行 doCreateBean() 方法創(chuàng)建這個(gè) Bean。

二級(jí)緩存

我們現(xiàn)在已經(jīng)知道,第三級(jí)緩存的目的是為了延遲代理對(duì)象的創(chuàng)建,因?yàn)槿绻麤]有依賴循環(huán)的話,那么就不需要為其提前創(chuàng)建代理,可以將它延遲到初始化完成之后再創(chuàng)建。

既然目的只是延遲的話,那么我們是不是可以不延遲創(chuàng)建,而是在實(shí)例化完成之后,就為其創(chuàng)建代理對(duì)象,這樣我們就不需要第三級(jí)緩存了。因此,我們可以將addSingletonFactory() 方法進(jìn)行改造。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(singletonFactory, "Singleton factory must not be null");
  synchronized (this.singletonObjects) {
    if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級(jí)緩存中不存在此對(duì)象
      object o = singletonFactory.getObject(); // 直接從工廠中獲取 Bean
      this.earlySingletonObjects.put(beanName, o); // 添加至二級(jí)緩存中
      this.registeredSingletons.add(beanName);
    }
  }
}

這樣的話,每次實(shí)例化完 Bean 之后就直接去創(chuàng)建代理對(duì)象,并添加到二級(jí)緩存中。測(cè)試結(jié)果是完全正常的,Spring 的初始化時(shí)間應(yīng)該也是不會(huì)有太大的影響,因?yàn)槿绻?Bean 本身不需要代理的話,是直接返回原始 Bean 的,并不需要走復(fù)雜的創(chuàng)建代理 Bean 的流程。

結(jié)論

測(cè)試證明,二級(jí)緩存也是可以解決循環(huán)依賴的。為什么 Spring 不選擇二級(jí)緩存,而要額外多添加一層緩存呢?

如果 Spring 選擇二級(jí)緩存來(lái)解決循環(huán)依賴的話,那么就意味著所有 Bean 都需要在實(shí)例化完成之后就立馬為其創(chuàng)建代理,而 Spring 的設(shè)計(jì)原則是在 Bean 初始化完成之后才為其創(chuàng)建代理。所以,Spring 選擇了三級(jí)緩存。但是因?yàn)檠h(huán)依賴的出現(xiàn),導(dǎo)致了 Spring 不得不提前去創(chuàng)建代理,因?yàn)槿绻惶崆皠?chuàng)建代理對(duì)象,那么注入的就是原始對(duì)象,這樣就會(huì)產(chǎn)生錯(cuò)誤。

到此這篇關(guān)于淺談Spring 解決循環(huán)依賴必須要三級(jí)緩存嗎的文章就介紹到這了,更多相關(guān)Spring  循環(huán)依賴三級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中的邏輯結(jié)構(gòu)詳解

    Java中的邏輯結(jié)構(gòu)詳解

    這篇文章主要介紹了Java中的邏輯結(jié)構(gòu)詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Java并發(fā)之搞懂讀寫鎖

    Java并發(fā)之搞懂讀寫鎖

    這篇文章主要介紹了Java并發(fā)之讀寫鎖,文中相關(guān)實(shí)例代碼詳細(xì),測(cè)試可用,具有一定參考價(jià)值,需要的朋友可以了解下,希望能夠給你帶來(lái)幫助
    2021-11-11
  • SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼

    SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼

    本文主要介紹了SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Springboot3.3 整合Cassandra 4.1.5的詳細(xì)過(guò)程

    Springboot3.3 整合Cassandra 4.1.5的詳細(xì)過(guò)程

    這篇文章主要介紹了Springboot3.3 整合Cassandra 4.1.5的詳細(xì)過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-06-06
  • JAVA系統(tǒng)中Spring?Boot應(yīng)用程序的配置文件application.yml使用詳解

    JAVA系統(tǒng)中Spring?Boot應(yīng)用程序的配置文件application.yml使用詳解

    這篇文章主要介紹了JAVA系統(tǒng)中Spring?Boot應(yīng)用程序的配置文件application.yml的相關(guān)資料,application.yml是Spring?Boot應(yīng)用程序的配置文件,定義了服務(wù)器、Spring、日志、安全及其他配置屬性,確保應(yīng)用程序正確啟動(dòng)和運(yùn)行,需要的朋友可以參考下
    2025-01-01
  • Java8 Stream中對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值的代碼示例

    Java8 Stream中對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值的代碼示例

    這篇文章主要介紹了Java8 Stream中如何對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值,文中通過(guò)代碼示例為大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2023-06-06
  • Spring中的依賴注入DI詳解

    Spring中的依賴注入DI詳解

    這篇文章主要介紹了Spring中的依賴注入DI詳解,組件之間依賴關(guān)系由容器在運(yùn)行期決定,形象的說(shuō),即由容器動(dòng)態(tài)的將依賴關(guān)系注入到組件之中,依賴注入的目的并非為軟件系統(tǒng)帶來(lái)更多功能,是為了提升組件重用的頻率,并為系統(tǒng)搭建一個(gè)靈活、可擴(kuò)展的平臺(tái),需要的朋友可以參考下
    2024-01-01
  • Java如何將int型數(shù)組轉(zhuǎn)為String型數(shù)組

    Java如何將int型數(shù)組轉(zhuǎn)為String型數(shù)組

    這篇文章主要介紹了Java如何將int型數(shù)組轉(zhuǎn)為String型數(shù)組,本文給大家分享具體實(shí)現(xiàn)思路結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性

    Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性

    這篇文章主要為大家介紹了Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Java遍歷Map對(duì)象集合的六種方式代碼示例

    Java遍歷Map對(duì)象集合的六種方式代碼示例

    Java中的Map是一種鍵值對(duì)映射的數(shù)據(jù)結(jié)構(gòu),它提供了一些常用的方法用于獲取、添加、刪除和修改元素,下面這篇文章主要給大家介紹了關(guān)于Java遍歷Map對(duì)象集合的六種方式,需要的朋友可以參考下
    2024-02-02

最新評(píng)論