一塊兒來讀Spring源碼吧(四)循環依賴踩坑筆記

源起

在開發過程當中,遇到須要把方法調用改成異步的狀況,原本覺得簡單得加個@Asyn在方法上就好了,沒想到項目啓動的時候報了以下的錯誤:spring

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'customerServiceImpl': 
Bean with name 'customerServiceImpl' has been injected into other beans [customerServiceImpl,followServiceImpl,cupidService] in its raw version as part of a circular reference, 
but has eventually been wrapped. This means that said other beans do not use the final version of the bean. 
This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

看了下好像報的是循環依賴的錯誤,可是Spring單例是支持循環依賴的,當時一臉懵逼。
拿着報錯去百度了下,說是多個動態代理致使的循環依賴報錯,也找到了報錯的地點,可是仍是不明白爲何會這樣,因此打算深刻源碼探個究竟,順便回顧下Bean的獲取流程和循環依賴的內容。緩存

模擬場景

用SpringBoot新建一個demo項目,由於原項目是有定義切面的,這裏也定義一個切面:app

@Aspect
@Component
public class TestAspect {

    @Pointcut("execution(public * com.example.demo.service.CyclicDependencyService.sameClassMethod(..))")
    private void testPointcut() {}

    @AfterReturning("testPointcut()")
    public void after(JoinPoint point) {
        System.out.println("在" + point.getSignature() + "以後乾點事情");
    }

}

而後新建一個注入本身的Service構成循環依賴,而後提供一個方法知足切點要求,而且加上@Async註解:異步

@Service
public class CyclicDependencyService {

    @Autowired
    private CyclicDependencyService cyclicDependencyService;

    public void test() {
        System.out.println("調用同類方法");
        cyclicDependencyService.sameClassMethod();
    }

    @Async
    public void sameClassMethod() {
        System.out.println("循環依賴中的異步方法");
        System.out.println("方法線程:" + Thread.currentThread().getName());
    }

}

還有別忘了給Application啓動類加上@EnableAsync和@EnableAspectJAutoProxy:ide

@EnableAsync
@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

最後打好斷點,開始debug。函數

debug

從Bean建立的的起點--AbstractBeanFactory#getBean開始post

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

首先會在緩存中查找,DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference):this

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

這裏一共有三級緩存:lua

  1. singletonObjects,保存初始化完成的單例bean實例;
  2. earlySingletonObjects,保存提早曝光的單例bean實例;
  3. singletonFactories,保存單例bean的工廠函數對象;

後面兩級都是爲了解決循環依賴設置的,具體查找邏輯在後續其餘狀況下調用會說明。線程

緩存中找不到,就要建立單例:

sharedInstance = getSingleton(beanName, () -> {
   try {
      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;
   }
});

調用DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory):

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    ...
    beforeSingletonCreation(beanName);
    ...
    singletonObject = singletonFactory.getObject();
    ...
    afterSingletonCreation(beanName);
    ...
    addSingleton(beanName, singletonObject);
    ...
}

建立先後分別作了這幾件事:

  1. 前,beanName放入singletonsCurrentlyInCreation,表示單例正在建立中
  2. 後,從singletonsCurrentlyInCreation中移除beanName
  3. 後,將建立好的bean放入singletonObjects,移除在singletonFactories和earlySingletonObjects的對象

建立單例調用getSingleton時傳入的工廠函數對象的getObject方法,實際上就是createBean方法,主要邏輯在AbstractAutowireCapableBeanFactory#doCreateBean中:

...
instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = instanceWrapper.getWrappedInstance();
...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
   populateBean(beanName, mbd, instanceWrapper);
   exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name '" + beanName + "' has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] in its raw version as part of a circular reference, but has eventually been " +
                  "wrapped. This means that said other beans do not use the final version of the " +
                  "bean. This is often the result of over-eager type matching - consider using " +
                  "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
         }
      }
   }
}

能夠看到報錯就是在這個方法裏拋出的,那麼這個方法就是重點中的重點。

首先實例化單例,instantiate,只是實例化獲取對象引用,尚未注入依賴。我debug時記錄的bean對象是CyclicDependencyService@4509

而後判斷bean是否須要提早暴露,須要知足三個條件:一、是單例;二、支持循環依賴;三、bean正在建立中,也就是到前面提到的singletonsCurrentlyInCreation中能查找到,全知足的話就會調用DefaultSingletonBeanRegistry#addSingletonFactory把beanName和單例工廠函數對象(匿名實現調用AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法)放入singletonFactories;

接着就是注入依賴,填充屬性,具體怎麼注入這裏就不展開了,最後會爲屬性cyclicDependencyService調用DefaultSingletonBeanRegistry.getSingleton(beanName, true),注意這裏和最開始的那次調用不同,isSingletonCurrentlyInCreation爲true,就會在singletonFactories中找到bean的單例工廠函數對象,也就是在上一步提早暴露時放入的,而後調用它的匿名實現AbstractAutowireCapableBeanFactory#getEarlyBeanReference:

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;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}

方法邏輯就是挨個調用實現了SmartInstantiationAwareBeanPostProcessor接口的後置處理器(如下簡稱BBP)的getEarlyBeanReference方法。一個一個debug下來,其餘都是原樣返回bean,只有AnnotationAwareAspectJAutoProxyCreator會把原bean(CyclicDependencyService@4509)存在earlyProxyReferences,而後將bean的代理返回(debug時記錄的返回對象是CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740)並放入earlySingletonObjects,再賦給屬性cyclicDependencyService。

public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}

屬性填充完成後就是調用初始化方法AbstractAutowireCapableBeanFactory#initializeBean:

...
invokeAwareMethods(beanName, bean);
...
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
...
invokeInitMethods(beanName, wrappedBean, mbd);
...
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
...

初始化主要分爲這幾步:

  1. 若是bean實現了BeanNameAware、BeanClassLoaderAware或BeanFactoryAware,把相應的資源放入bean;
  2. 順序執行BBP的postProcessBeforeInitialization方法;
  3. 若是實現了InitializingBean就執行afterPropertiesSet方法,而後執行本身的init-method;
  4. 順序執行BBP的postProcessAfterInitialization。

debug的時候發現是第4步改變了bean,先執行AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

這裏會獲取並移除以前存在earlyProxyReferences的bean(CyclicDependencyService@4509),由於和當前bean是同一個對象,因此什麼都沒作直接返回。隨後會執行AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization:

if (isEligible(bean, beanName)) {
   ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
   if (!proxyFactory.isProxyTargetClass()) {
      evaluateProxyInterfaces(bean.getClass(), proxyFactory);
   }
   proxyFactory.addAdvisor(this.advisor);
   customizeProxyFactory(proxyFactory);
   return proxyFactory.getProxy(getProxyClassLoader());
}

先判斷bean是否有須要代理,由於CyclicDependencyService有方法帶有@Async註解就須要代理,返回代理對象是CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273

返回的代理對象賦值給AbstractAutowireCapableBeanFactory#doCreateBean方法內的exposedObject,接下來就到了檢查循環依賴的地方了:

if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name '" + beanName + "' has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] in its raw version as part of a circular reference, but has eventually been " +
                  "wrapped. This means that said other beans do not use the final version of the " +
                  "bean. This is often the result of over-eager type matching - consider using " +
                  "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
         }
      }
   }
}

首先從earlySingletonObjects裏拿到前面屬性填充時放入的bean代理(CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740),不爲空的話就比較bean和exposedObject,分別是CyclicDependencyService@4509CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273,很明顯不是同一個對象,而後會判斷allowRawInjectionDespiteWrapping屬性和是否有依賴的bean,而後判斷這些bean是不是真實依賴的,一旦存在真實依賴的bean,就會拋出BeanCurrentlyInCreationException。

總結

總結下Spring解決循環依賴的思路:在建立bean時,對於知足提早曝光條件的單例,會把該單例的工廠函數對象放入三級緩存中的singletonFactories中;而後在填充屬性時,若是存在循環依賴,必然會嘗試獲取該單例,也就是執行以前放入的工廠函數的匿名實現,這時候拿到的有多是原bean對象,也有多是被某些BBP處理過返回的代理對象,會放入三級緩存中的earlySingletonObjects中;接着bean開始初始化,結果返回的有多是原bean對象,也有多是代理對象;最後對於知足提早曝光的單例,若是真的有提早曝光的動做,就會去檢查初始化後的bean對象是否是原bean對象是同一個對象,只有不是的狀況下才可能拋出異常。重點就在於存在循環依賴的狀況下,初始化過的bean對象是否是跟原bean是同一個對象

從以上的debug過程能夠看出,是AsyncAnnotationBeanPostProcessor這個BBP在初始化過程當中改變了bean,使得結果bean和原bean不是一個對象,而AnnotationAwareAspectJAutoProxyCreator則是在填充屬性獲取提早曝光的對象時把原始bean緩存起來,返回代理的bean。而後在初始化時執行它的postProcessAfterInitialization方法時若是傳入的bean是以前緩存的原始bean,就直接返回,不進行代理。若是其餘BBP也都沒有改變bean的話,初始化事後的bean就是跟原始bean是同一個對象,這時就會把提早曝光的對象(代理過的)做爲最終生成的bean。

相關文章
相關標籤/搜索