Spring源碼分析之如何解決循環(huán)依賴

本篇內(nèi)容介紹了“Spring源碼分析之如何解決循環(huán)依賴”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)專注于企業(yè)成都全網(wǎng)營銷、網(wǎng)站重做改版、柏鄉(xiāng)網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5場景定制、商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價格優(yōu)惠性價比高,為柏鄉(xiāng)等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

首先,我們需要明白什么是循環(huán)依賴?簡單來說就是A對象創(chuàng)建過程中需要依賴B對象,而B對象創(chuàng)建過程中同樣也需要A對象,所以A創(chuàng)建時需要先去把B創(chuàng)建出來,但B創(chuàng)建時又要先把A創(chuàng)建出來...死循環(huán)有木有...

Spring源碼分析之如何解決循環(huán)依賴

循環(huán)依賴

那么在Spring中,有多少種循環(huán)依賴的情況呢?大部分人只知道兩個普通的Bean之間的循環(huán)依賴,而Spring中其實(shí)存在三種對象(普通Bean,工廠Bean,代理對象),他們之間都會存在循環(huán)依賴,這里我給列舉出來,大致分別以下幾種:

  • 普通Bean與普通Bean之間
  • 普通Bean與代理對象之間
  • 代理對象與代理對象之間
  • 普通Bean與工廠Bean之間
  • 工廠Bean與工廠Bean之間
  • 工廠Bean與代理對象之間

那么,在Spring中是如何解決這個問題的呢?

1. 普通Bean與普通Bean

首先,我們先設(shè)想一下,如果讓我們自己來編碼,我們會如何解決這個問題? 

栗子

現(xiàn)在我們有兩個互相依賴的對象A和B

public class NormalBeanA {

 private NormalBeanB normalBeanB;

 public void setNormalBeanB(NormalBeanB normalBeanB) {
  this.normalBeanB = normalBeanB;
 }
}
 
public class NormalBeanB {

 private NormalBeanA normalBeanA;

 public void setNormalBeanA(NormalBeanA normalBeanA) {
  this.normalBeanA = normalBeanA;
 }
}
 

然后我們想要讓他們彼此都含有對象

public class Main {

 public static void main(String[] args) {
  // 先創(chuàng)建A對象
  NormalBeanA normalBeanA = new NormalBeanA();
  // 創(chuàng)建B對象
  NormalBeanB normalBeanB = new NormalBeanB();
  // 將A對象的引用賦給B
  normalBeanB.setNormalBeanA(normalBeanA);
  // 再將B賦給A
  normalBeanA.setNormalBeanB(normalBeanB);
 }
}
 

發(fā)現(xiàn)了嗎?我們并沒有先創(chuàng)建一個完整的A對象,而是先創(chuàng)建了一個空殼對象(Spring中稱為早期對象),將這個早期對象A先賦給了B,使得得到了一個完整的B對象,再將這個完整的B對象賦給A,從而解決了這個循環(huán)依賴問題,so easy!

那么Spring中是不是也這樣做的呢?我們就來看看吧~ 

Spring中的解決方案

先來到創(chuàng)建Bean的方法

AbstractAutowireCapableBeanFactory#doCreateBean

假設(shè)此時在創(chuàng)建A

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
  // beanName -> A
  // 實(shí)例化A
  BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
  // 是否允許暴露早期對象
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                    isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 將獲取早期對象的回調(diào)方法放到三級緩存中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
}
 

addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  synchronized (this.singletonObjects) {
   // 單例緩存池中沒有該Bean
   if (!this.singletonObjects.containsKey(beanName)) {
    // 將回調(diào)函數(shù)放入三級緩存
    this.singletonFactories.put(beanName, singletonFactory);
    this.earlySingletonObjects.remove(beanName);
    this.registeredSingletons.add(beanName);
   }
  }
 }
 

ObjectFactory是一個函數(shù)式接口

在這里,我們發(fā)現(xiàn)在創(chuàng)建Bean時,Spring不管三七二十一,直接將一個獲取早期對象的回調(diào)方法放進(jìn)了一個三級緩存中,我們再來看一下回調(diào)方法的邏輯

getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  Object exposedObject = bean;
  // 調(diào)用BeanPostProcessor對早期對象進(jìn)行處理,在Spring的內(nèi)置處理器中,并無相關(guān)的處理邏輯
  // 如果開啟了AOP,將引入一個AnnotationAwareAspectJAutoProxyCreator,此時將可能對Bean進(jìn)行動態(tài)代理
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
        SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
        exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
      }
    }
  }
  return exposedObject;
}
 

在這里,如果沒有開啟AOP,或者該對象不需要動態(tài)代理,會直接返回原對象

此時,已經(jīng)將A的早期對象緩存起來了,接下來在填充屬性時會發(fā)生什么呢?

相信大家也應(yīng)該想到了,A對象填充屬性時必然發(fā)現(xiàn)依賴了B對象,此時就將轉(zhuǎn)頭創(chuàng)建B,在創(chuàng)建B時同樣會經(jīng)歷以上步驟,此時就該B對象填充屬性了,這時,又將要轉(zhuǎn)頭創(chuàng)建A,那么,現(xiàn)在會有什么不一樣的地方呢?我們看看getBean的邏輯吧

doGetBean

protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){
  // 此時beanName為A
  String beanName = transformedBeanName(name);
  // 嘗試從三級緩存中獲取bean,這里很關(guān)鍵
  Object sharedInstance = getSingleton(beanName);
}
 
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 從單例緩存池中獲取,此時仍然是取不到的
  Object singletonObject = this.singletonObjects.get(beanName);
  // 獲取不到,判斷bean是否正在創(chuàng)建,沒錯,此時A確實(shí)正在創(chuàng)建
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 由于現(xiàn)在仍然是在同一個線程,基于同步鎖的可重入性,此時不會阻塞
    synchronized (this.singletonObjects) {
      // 從早期對象緩存池中獲取,這里是沒有的
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 從三級緩存中獲取回調(diào)函數(shù),此時就獲取到了我們在創(chuàng)建A時放入的回調(diào)函數(shù)
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 調(diào)用回調(diào)方法獲取早期bean,由于我們現(xiàn)在討論的是普通對象,所以返回原對象
          singletonObject = singletonFactory.getObject();
          // 將早期對象放到二級緩存,移除三級緩存
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  // 返回早期對象A
  return singletonObject;
}
 

震驚!此時我們就拿到了A的早期對象進(jìn)行返回,所以B得以被填充屬性,B創(chuàng)建完畢后,又將返回到A填充屬性的過程,A也得以被填充屬性,A也創(chuàng)建完畢,這時,A和B都創(chuàng)建好了,循環(huán)依賴問題得以收場~

Spring源碼分析之如何解決循環(huán)依賴

普通Bean和普通Bean之間的問題就到這里了,不知道小伙伴們有沒有暈?zāi)亍?nbsp;

2. 普通Bean和代理對象

普通Bean和代理對象之間的循環(huán)依賴與兩個普通Bean的循環(huán)依賴其實(shí)大致相同,只不過是多了一次動態(tài)代理的過程,我們假設(shè)A對象是需要代理的對象,B對象仍然是一個普通對象,然后,我們開始創(chuàng)建A對象。

剛開始創(chuàng)建A的過程與上面的例子是一模一樣的,緊接著自然是需要創(chuàng)建B,然后B依賴了A,于是又倒回去創(chuàng)建A,此時,再次走到去緩存池獲取的過程。

// 從三級緩存中獲取回調(diào)函數(shù)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
  // 調(diào)用回調(diào)方法獲取早期bean,此時返回的是一個A的代理對象
  singletonObject = singletonFactory.getObject();
  // 將早期對象放到二級緩存,移除三級緩存
  this.earlySingletonObjects.put(beanName, singletonObject);
  this.singletonFactories.remove(beanName);
}
 

這時就不太一樣了,在singletonFactory.getObject()時,由于此時A是需要代理的對象,在調(diào)用回調(diào)函數(shù)時,就會觸發(fā)動態(tài)代理的過程

AbstractAutoProxyCreator#getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) {
  // 生成一個緩存Key
  Object cacheKey = getCacheKey(bean.getClass(), beanName);
  // 放入緩存中,用于在初始化后調(diào)用該后置處理器時判斷是否進(jìn)行動態(tài)代理過
  this.earlyProxyReferences.put(cacheKey, bean);
  // 將對象進(jìn)行動態(tài)代理
  return wrapIfNecessary(bean, beanName, cacheKey);
}
 

此時,B在創(chuàng)建時填充的屬性就是A的代理對象了,B創(chuàng)建完畢,返回到A的創(chuàng)建過程,但此時的A仍然是一個普通對象,可B引用的A已經(jīng)是個代理對象了,不知道小伙伴看到這里有沒有迷惑呢?

不急,讓我們繼續(xù)往下走,填充完屬性自然是需要初始化的,在初始化后,會調(diào)用一次后置處理器,我們看看會不會有答案吧 

初始化

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
 //...省略前面的步驟...
  // 調(diào)用初始化方法
  invokeInitMethods(beanName, wrappedBean, mbd);
  // 處理初始化后的bean
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
 

在處理初始化后的bean,又會調(diào)用動態(tài)代理的后置處理器了

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 判斷緩存中是否有該對象,有則說明該對象已被動態(tài)代理,跳過
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}
 

不知道小伙伴發(fā)現(xiàn)沒有,earlyProxyReferences這個緩存可不就是我們在填充B的屬性,進(jìn)而從緩存中獲取A時放進(jìn)去的嗎?不信您往上翻到getEarlyBeanReference的步驟看看~

所以,此時并未進(jìn)行任何處理,依舊返回了我們的原對象A,看來這里并沒有我們要的答案,那就繼續(xù)吧~

// 是否允許暴露早期對象
if (earlySingletonExposure) {
  // 從緩存池中獲取早期對象
  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
    // bean為初始化前的對象,exposedObject為初始化后的對象
    // 判斷兩對象是否相等,基于上面的分析,這兩者是相等的
    if (exposedObject == bean) {
      // 將早期對象賦給exposedObject
      exposedObject = earlySingletonReference;
    }
  }
}
 

我們來分析一下上面的邏輯,getSingleton從緩存池中獲取早期對象返回的是什么呢?

synchronized (this.singletonObjects) {
  // 從早期對象緩存池中獲取,此時就拿到了我們填充B屬性時放入的A的代理對象
  singletonObject = this.earlySingletonObjects.get(beanName);
  if (singletonObject == null && allowEarlyReference) {
    // 從三級緩存中獲取回調(diào)函數(shù)
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
      // 調(diào)用回調(diào)方法獲取早期bean
      singletonObject = singletonFactory.getObject();
      // 將早期對象放到二級緩存,移除三級緩存
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
    }
  }
}
 

發(fā)現(xiàn)了嗎?此時我們就獲取到了A的代理對象,然后我們又把這個對象賦給了exposedObject,此時創(chuàng)建對象的流程走完,我們得到的A不就是個代理對象了嗎~

此次栗子是先創(chuàng)建需要代理的對象A,假設(shè)我們先創(chuàng)建普通對象B會發(fā)生什么呢? 

3. 代理對象與代理對象

代理對象與代理對象的循環(huán)依賴是怎么樣的呢?解決過程又是如何呢?這里就留給小伙伴自己思考了,其實(shí)和普通Bean與代理對象是一模一樣的,小伙伴想想是不是呢,這里我就不做分析了。 

4. 普通Bean與工廠Bean

這里所說的普通Bean與工廠Bean并非指bean與FactoryBean,這將毫無意義,而是指普通Bean與FactoryBean的getObject方法產(chǎn)生了循環(huán)依賴,因?yàn)?code>FactoryBean最終產(chǎn)生的對象是由getObject方法所產(chǎn)出。我們先來看看栗子吧~

假設(shè)工廠對象A依賴普通對象B,普通對象B依賴普通對象A。

小伙伴看到這里就可能問了,誒~你這不對呀,怎么成了「普通對象B依賴普通對象A」呢?不應(yīng)該是工廠對象A嗎?是這樣的,在Spring中,由于普通對象A是由工廠對象A產(chǎn)生,所有在普通對象B想要獲取普通對象A時,其實(shí)最終尋找調(diào)用的是工廠對象A的getObject方法,所以只要普通對象B依賴普通對象A就可以了,Spring會自動幫我們把普通對象B和工廠對象A聯(lián)系在一起。

小伙伴,哦~

普通對象A

public class NormalBeanA {

 private NormalBeanB normalBeanB;

 public void setNormalBeanB(NormalBeanB normalBeanB) {
  this.normalBeanB = normalBeanB;
 }
}
 

工廠對象A

@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
 @Autowired
 private ApplicationContext context;

 @Override
 public NormalBeanA getObject() throws Exception {
  NormalBeanA normalBeanA = new NormalBeanA();
  NormalBeanB normalBeanB = context.getBean("normalBeanB", NormalBeanB.class);
  normalBeanA.setNormalBeanB(normalBeanB);
  return normalBeanA;
 }

 @Override
 public Class<?> getObjectType() {
  return NormalBeanA.class;
 }
}
 

普通對象B

@Component
public class NormalBeanB {
 @Autowired
 private NormalBeanA normalBeanA;
}
 

假設(shè)我們先創(chuàng)建對象A

由于FactoryBean和Bean的創(chuàng)建過程是一樣的,只是多了步getObject,所以我們直接定位到調(diào)用getObject入口

if (mbd.isSingleton()) {
  // 開始創(chuàng)建bean
  sharedInstance = getSingleton(beanName, () -> {
    // 創(chuàng)建bean
    return createBean(beanName, mbd, args);
  });
  // 處理FactoryBean
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
 
protected Object getObjectForBeanInstance(
   Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
 // 先嘗試從緩存中獲取,保證多次從工廠bean獲取的bean是同一個bean
  object = getCachedObjectForFactoryBean(beanName);
  if (object == null) {
    // 從FactoryBean獲取對象
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
  }
}
 
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
 // 加鎖,防止多線程時重復(fù)創(chuàng)建bean
  synchronized (getSingletonMutex()) {
    // 這里是Double Check
    Object object = this.factoryBeanObjectCache.get(beanName);
    if (object == null) {
      // 獲取bean,調(diào)用factoryBean的getObject()
      object = doGetObjectFromFactoryBean(factory, beanName);
    }
    // 又從緩存中取了一次,why? 我們慢慢分析
    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
      object = alreadyThere;
    }else{
      // ...省略初始化bean的邏輯...
      // 將獲取到的bean放入緩存
      this.factoryBeanObjectCache.put(beanName, object);
    }
  }
}
 
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName){
  return factory.getObject();
}
 

現(xiàn)在,就走到了我們自定義的getObject方法,由于我們調(diào)用了context.getBean("normalBeanB", NormalBeanB.class),此時,將會去創(chuàng)建B對象,在創(chuàng)建過程中,先將B的早期對象放入三級緩存,緊接著填充屬性,發(fā)現(xiàn)依賴了A對象,又要倒回來創(chuàng)建A對象,從而又回到上面的邏輯,再次調(diào)用我們自定義的getObject方法,這個時候會發(fā)生什么呢?

又要去創(chuàng)建B對象...(Spring:心好累)

但是!此時我們在創(chuàng)建B時,是直接通過getBean在緩存中獲取到了B的早期對象,得以返回了!于是我們自定義的getObject調(diào)用成功,返回了一個完整的A對象!

但是此時FactoryBean的緩沖中還是什么都沒有的。

// 又從緩存中取了一次
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
  object = alreadyThere;
}
 

這一次取alreadyThere必然是null,流程繼續(xù)執(zhí)行,將此時將獲取到的bean放入緩存

this.factoryBeanObjectCache.put(beanName, object);
 

從FactoryBean獲取對象的流程結(jié)束,返回到創(chuàng)建B的過程中,B對象此時的屬性也得以填充,再返回到第一次創(chuàng)建A的過程,也就是我們第一次調(diào)用自定義的getObject方法,調(diào)用完畢,返回到這里

// 獲取bean,調(diào)用factoryBean的getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
  object = alreadyThere;
 

那么,此時this.factoryBeanObjectCache.get(beanName)能從緩沖中拿到對象了嗎?有沒有發(fā)現(xiàn),拿到了剛剛B對象填充屬性時再次創(chuàng)建A對象放進(jìn)去的!

所以,明白這里為什么要再次從緩存中獲取了吧?就是為了解決由于循環(huán)依賴時調(diào)用了兩次自定義的getObject方法,從而創(chuàng)建了兩個不相同的A對象,保證我們返回出去的A對象唯一!

怕小伙伴暈了,畫個圖給大家

Spring源碼分析之如何解決循環(huán)依賴 

5. 工廠Bean與工廠Bean之間

我們已經(jīng)舉例4種循環(huán)依賴的栗子,Spring都有所解決,那么有沒有Spring也無法解決的循環(huán)依賴問題呢?

有的!就是這個FactoryBeanFactoryBean的循環(huán)依賴!

假設(shè)工廠對象A依賴工廠對象B,工廠對象B依賴工廠對象A,那么,這次的栗子會是什么樣呢?

普通對象

public class NormalBeanA {

 private NormalBeanB normalBeanB;

 public void setNormalBeanB(NormalBeanB normalBeanB) {
  this.normalBeanB = normalBeanB;
 }
}
 
public class NormalBeanB {

 private NormalBeanA normalBeanA;

 public void setNormalBeanA(NormalBeanA normalBeanA) {
  this.normalBeanA = normalBeanA;
 }
}
 

工廠對象

@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
 @Autowired
 private ApplicationContext context;

 @Override
 public NormalBeanA getObject() throws Exception {
  NormalBeanA normalBeanA = new NormalBeanA();
  NormalBeanB normalBeanB = context.getBean("factoryBeanB", NormalBeanB.class);
  normalBeanA.setNormalBeanB(normalBeanB);
  return normalBeanA;
 }

 @Override
 public Class<?> getObjectType() {
  return NormalBeanA.class;
 }
}
 
@Component
public class FactoryBeanB implements FactoryBean<NormalBeanB> {
 @Autowired
 private ApplicationContext context;
 @Override
 public NormalBeanB getObject() throws Exception {
  NormalBeanB normalBeanB = new NormalBeanB();
  NormalBeanA normalBeanA = context.getBean("factoryBeanA", NormalBeanA.class);
  normalBeanB.setNormalBeanA(normalBeanA);
  return normalBeanB;
 }

 @Override
 public Class<?> getObjectType() {
  return NormalBeanB.class;
 }
}
 

首先,我們開始創(chuàng)建對象A,此時為調(diào)用工廠對象A的getObject方法,轉(zhuǎn)而去獲取對象B,便會走到工廠對象B的getObject方法,然后又去獲取對象A,又將調(diào)用工廠對象A的getObject,再次去獲取對象B,于是再次走到工廠對象B的getObject方法......此時,已經(jīng)歷了一輪循環(huán),卻沒有跳出循環(huán)的跡象,妥妥的死循環(huán)了。

我們畫個圖吧~

Spring源碼分析之如何解決循環(huán)依賴

沒錯!這個圖就是這么簡單,由于始終無法創(chuàng)建出一個對象,不管是早期對象或者完整對象,使得兩個工廠對象反復(fù)的去獲取對方,導(dǎo)致陷入了死循環(huán)。

那么,我們是否有辦法解決這個問題呢?

我的答案是無法解決,如果有想法的小伙伴也可以自己想一想哦~

我們發(fā)現(xiàn),在發(fā)生循環(huán)依賴時,只要循環(huán)鏈中的某一個點(diǎn)可以先創(chuàng)建出一個早期對象,那么在下一次循環(huán)時,就會使得我們能夠獲取到早期對象從而跳出循環(huán)!

而由于工廠對象與工廠對象間是無法創(chuàng)建出這個早期對象的,無法滿足跳出循環(huán)的條件,導(dǎo)致變成了死循環(huán)。

那么此時Spring中會拋出一個什么樣的異常呢?

當(dāng)然是棧溢出異常啦!兩個工廠對象一直相互調(diào)用,不斷開辟棧幀,可不就是棧溢出有木有~ 

6. 工廠對象與代理對象

上面的情況是無法解決循環(huán)依賴的,那么這個情況可以解決嗎?

答案是可以的!

我們分析了,一個循環(huán)鏈?zhǔn)欠衲軌虻玫浇K止,關(guān)鍵在于是否能夠在某個點(diǎn)創(chuàng)建出一個早期對象(臨時對象),而代理對象在doCreateBean時,是會生成一個早期對象放入三級緩存的,于是該循環(huán)鏈得以終結(jié)。

“Spring源碼分析之如何解決循環(huán)依賴”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

文章名稱:Spring源碼分析之如何解決循環(huán)依賴
鏈接地址:http://muchs.cn/article0/pisdio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供標(biāo)簽優(yōu)化、網(wǎng)站設(shè)計(jì)公司微信小程序、品牌網(wǎng)站制作網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)