天天看點

這麼回答【循環依賴】助力輕松拿下阿裡P6

這麼回答【循環依賴】助力輕松拿下阿裡P6

一篇文章徹底搞定Spring循環依賴

lecture:波哥

一、什麼是循環依賴

看下圖:

這麼回答【循環依賴】助力輕松拿下阿裡P6

  上圖是循環依賴的三種情況,雖然方式有點不一樣,但是循環依賴的本質是一樣的,就你的完整建立要依賴與我,我的完整建立也依賴于你。互相依賴進而沒法完整建立造成失敗。

二、構造注入

1. 案例示範

  構造注入就是依賴關系在構造方法中關聯。

@Component
@Scope(value = "prototype")
public class BeanA {

    private BeanB beanB;

    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    private BeanA beanA;

    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}      

  然後我們可以通過啟動容器來檢視,在Spring中構造注入的​

​循環依賴​

​在Spring中是沒有辦法解決的。隻是通過抛出異常來處理了。

@Configuration
@ComponentScan
public class Demo1Main {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(Demo1Main.class);
    }
}      
這麼回答【循環依賴】助力輕松拿下阿裡P6

2.解決方案

  那麼在Spring中是如何發現執行個體的Bean對象有​

​循環依賴​

​的問題,并抛出異常的呢?其實實作邏輯也比較簡單,如下圖:

這麼回答【循環依賴】助力輕松拿下阿裡P6

  實作的過程也很簡單,就是在建立對象之前判斷該對象在這個臨時容器中是否存在,如果不存在就開始建立,如果存在則說明産生了​

​循環依賴​

​。對象建立完成後就把該對象從容器中移除。對應的看看Spring的源碼中的處理。

這麼回答【循環依賴】助力輕松拿下阿裡P6

3.單例分析

  先來看單例的處理,進入到​

​getSingleton​

​​方法中。我們需要關注的是兩個方法​

​beforeSingletonCreation(beanName)​

​​和​

​afterSingletonCreation(beanName);​

這麼回答【循環依賴】助力輕松拿下阿裡P6
這麼回答【循環依賴】助力輕松拿下阿裡P6

beforeSingletonCreation方法

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
    }
  }      

上面的代碼很清楚的看到,如果已經存在就抛出循環依賴的錯誤。而且因為是單例,是以容器也沒有通過​

​ThreadLocal​

​來處理了。

對應的​

​afterSingletonCreation​

​方法就是移走了。

protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
  }      

4.原型分析

  原型Bean思路差不多,但是實作方式差別有點大,我們先來看看​

​beforePrototypeCreation(beanName)​

​​和​

​afterPrototypeCreation(beanName);​

​這兩個方法。

// 資料存儲在了 ThreadLocal 中
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
      new NamedThreadLocal<>("Prototype beans currently in creation");

protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
      this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String) {
      Set<String> beanNameSet = new HashSet<>(2);
      beanNameSet.add((String) curVal);
      beanNameSet.add(beanName);
      this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
      Set<String> beanNameSet = (Set<String>) curVal;
      beanNameSet.add(beanName);
    }
  }      
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();
      }
    }
  }      

可以看到這兩個方法都隻是在​

​ThreadLocal​

​中錯了BeanName的寫入和移除的操作。并沒有做相關的檢查,而這個檢查其實是在

這麼回答【循環依賴】助力輕松拿下阿裡P6

這樣一來Spring中的構造注入的循環依賴針對單例或是原型的處理我們就清楚了!

三、依賴注入

1.案例示範

  設值注入就是通過setter方法來完成屬性的指派。

@Component
public class UserD {

    @Autowired
    private UserC userC;
}

@Component
public class UserC {
    @Autowired
    private UserD userD;

}      

寫主方法測試

@Configuration
@ComponentScan
public class Demo2Main {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(Demo2Main.class);
        System.out.println(ac.getBean(UserC.class));
    }
}      

我們可以看到在單例的情況下,Spring解決了​

​循環依賴​

​的問題了。

這麼回答【循環依賴】助力輕松拿下阿裡P6

但是在兩個對象都是原型的情況下就解決不了了。

@Component
@Scope(value = "prototype")
public class UserC {
    @Autowired
    private UserD userD;
    
}

@Component
@Scope(value = "prototype")
public class UserD {

    @Autowired
    private UserC userC;
}      

啟動測試我們可以看到出現了循環依賴的錯誤提示了

這麼回答【循環依賴】助力輕松拿下阿裡P6

當然,如果我們給其中的一個設定為單例,那麼循環依賴問題即可解決。原理,我們分析下就清楚了。

2.解決方案

  針對依賴注入這種情況下産生的​

​循環依賴​

​​問題,我們的解決方案是​

​提前暴露​

​。

這麼回答【循環依賴】助力輕松拿下阿裡P6

  但是在Spring中對于Bean執行個體因為AOP的存在,我們可能傳回的并不是原始的Bean對象,而是被AOP增強的代理對象,這時在Spring中需要使用到三級緩存來處理。我們具體來看看Spring中的處理方案。

3.原理分析

  然後我們來具體看看在Spring中對于依賴注入的​

​循環依賴​

​​的解決。進入到​

​AbstractAutowireCapableBeanFactory​

​​的​

​doCreateBean​

​方法中。

這麼回答【循環依賴】助力輕松拿下阿裡P6

  上面看到了具體建立Bean對象的方法​

​createBeanInstance​

​方法中,然後在下面中比較重要的兩個的方法

這麼回答【循環依賴】助力輕松拿下阿裡P6

  那麼對于的​

​提前暴露​

​​應該要在​

​populateBean​

​​方法之前實作。往前面找看到了​

​earlySingletonExposure​

​​變量,辨別的就是是否支援​

​提前暴露​

​。

// 判斷條件: 1.是否是單例Bean 2.是否支援循環依賴 3.是否是目前正在建立的Bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));      

  如果上面的條件成立就會走下面的代碼

if (earlySingletonExposure) {
      // 把 Lambda 表達式存儲在了三級緩存中 
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
// 對應的 addSingletonFactory 方法
  protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
            // 該單例對象還未建立過
      if (!this.singletonObjects.containsKey(beanName)) {
                // 把 上面的 Lambda 表達式 存儲在了 三級緩存中
        this.singletonFactories.put(beanName, singletonFactory);
                // 對應的二級緩存置空
        this.earlySingletonObjects.remove(beanName);
                // 記錄該單例對象名稱
        this.registeredSingletons.add(beanName);
      }
    }
  }      
這麼回答【循環依賴】助力輕松拿下阿裡P6

然後我們需要在AOP建立對應的代理增強對象之後再看是如果來暴露代理對象的。

這麼回答【循環依賴】助力輕松拿下阿裡P6

也就是我們看的關鍵是​

​getSingleton​

​方法

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // 從一級緩存中擷取對象
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 沒有擷取到建立好的Bean單例,同時目前正在建立Bean對象
        // 從 二級緩存中擷取 半層品 對象
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         // 二級緩存中沒有需要擷取的Bean 半成品對象
         synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // 一級緩存中沒有
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                   // 二級緩存 沒有  就取出之前存儲在三級緩存中的 Lambda 表達式
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                      // 根據Lambda表達式 擷取到Bean的 代理對象
                     singletonObject = singletonFactory.getObject();
                      // 把Bean的半成品對象存儲在二級緩存中
                     this.earlySingletonObjects.put(beanName, singletonObject);
                      // 清除 一級緩存的 對象
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
    // 傳回對應的半成品對象
   return singletonObject;
}      

然後對應的擷取代理對象的邏輯是在​

​singletonFactory.getObject()​

​中。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean; // 原始Bean
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
                // 通過每個BeanPostProcessor 對Bean對象做後置處理
        if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
          SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    // 遞歸擷取對應的 代理對象
          exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
        }
      }
    }
        // 傳回 對應的增強Bean
    return exposedObject;
  }      

具體邏輯如下圖:

這麼回答【循環依賴】助力輕松拿下阿裡P6

當依賴關系完成後。會進入到前面的​

​getSingleton​

​方法中。

這麼回答【循環依賴】助力輕松拿下阿裡P6

然後還要做對應的二級緩存和一級緩存資料處理

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
            // 建立好的單例Bean會存儲在 singletonObjects 這個Map 中,也就是一級緩存中
      this.singletonObjects.put(beanName, singletonObject);
            // 移除三級緩存的Lambda表達式
      this.singletonFactories.remove(beanName);
            // 移除二級緩存中的Bean的半成品對象,移除提前暴露的對象
      this.earlySingletonObjects.remove(beanName);
            // 注冊 單例
      this.registeredSingletons.add(beanName);
    }
  }      

繼續閱讀