「Spring源码」循环依赖解析&遇到的bug分析

656 阅读5分钟
  • 本篇分析内容
    • Spring循环依赖源码解析
    • @Async引发的bug分析

闲聊

本来打算开开信心摸鱼看源码了,突然被问了一个bug,这好奇心就忍不住了,循环依赖照理来说Spring应该处理好了,怎么还会有问题呢?正好之前鸽了好久没写了,就借此机会分析下,先贴报错:

org.springframework.beans.factory.BeanCurrentlyInCreationException:
 Error creating bean with name 'XXXXXA': 
 Bean with name 'XXXXXA' has been injected into other beans [XXXXXB] 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循环依赖源码解析

@Component
public class A {
   @Autowired
   private B b;

   public void sayHello(){
      System.out.println("hello");
   }
}

@Service
public class B {
   @Autowired
   private C c;

   @Async
   public void sayHello(){
      System.out.println("hello");
   }
}

@Service
public class C {
   @Autowired
   private D d;

   public void sayHello(){
      System.out.println("hello");
   }
}

@Component
public class D {
   @Autowired
   private A a;

   public void sayHello(){
      System.out.println("hello");
   }
}

@ComponentScan("org.springframework.test.self.annotation")
public class Demo {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();

      applicationContext.register(A.class);
      applicationContext.register(B.class);
      applicationContext.register(C.class);
      applicationContext.register(D.class);

      applicationContext.refresh();
      A bean = (A) applicationContext.getBean("a");
      bean.sayHello();
   }
}

前置说明

首先我先申明下,真的不知道谁取得一/二/三级缓存这几个名字的,但是很恐怖的事大家都在用,就是几个HashMap整这么花里胡哨的,Spring里面缓存有很多,叫四级,五级,六级。。。。。。n+级? 为了方便大家理解还是沿用了这几个名字了。我看了下基本就是这个方法里面用到的这三个缓存org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean),英文原版注释也放出来,各位怎么方便记怎么来

//一级缓存(?)存放创建完成,初始完成的bean
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存(?)存放bean工厂
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存(?),循环依赖中用到,三级缓存创建bean,此时刚创建完,值没有设置
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

流程分析

总体流程

截图工具有限,无法截长图,答题流程就是如下,创建A->发现需要B->创建B...上面已经给了大概的流程图了,感觉已经很详细了,这里就贴一下程序调用栈

image.png 核心程序为org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 前面Spring源码也是详细分析了,感兴趣的可以自行阅读,这里呢讲一个大概流程。

创建(实例化)Bean->加入三级缓存->依赖注入->初始化->循环依赖检查->返回

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

   // Instantiate the bean.持有创建出来的Bean对象
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {//单例,先把缓存中的同名Bean清除
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   //TODO 实际创建bean
   if (instanceWrapper == null) {
      //可以理解成调用构造函数创建实例,封装进了BeanWrapper
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   Object bean = instanceWrapper.getWrappedInstance();
   Class<?> beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }

   // Allow post-processors to modify the merged bean definition.
   synchronized (mbd.postProcessingLock) {
      if (!mbd.postProcessed) {
         try {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
         }
         catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Post-processing of merged bean definition failed", ex);
         }
         mbd.postProcessed = true;
      }
   }

   // Eagerly cache singletons to be able to resolve circular references
   // even when triggered by lifecycle interfaces like BeanFactoryAware.
   // 缓存单例,以便即使在由 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);
      //bean初始化,调用调用各种前置处理,后置处理等等
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
         throw (BeanCreationException) ex;
      }
      else {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
      }
   }

   //如果允许解决循环依赖的标识
   if (earlySingletonExposure) {
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            //依赖这个bean的所有bean
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               //如果依赖这个bean的bean创建完成,而初始化完成的bean和注入给其他bean不一样,spring就认为是异常了
               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 " +
                     "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   // Register bean as disposable.
   try {
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

查询缓存

对于循环依赖A->B->C->D->A到最后一步创建A的时候,这时候肯定不能继续下去,因为再下去就死循环了

这个时候缓存的情况:

singletonObjects(一级缓存中)没有A,

earlySingletonObjects(二级缓存中)没有A,

singletonFactories(三级缓存中)有A

此时singletonFactories(三级缓存中)有但是还没有初始化,这个时候,就把还没有初始化的bean返回。让其他几个完成创建挨个出栈。三级缓存也相应的删除,创建对应的bean加入到二级缓存中,到时候普通缓存查询也能够返回该实例。

image.png

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock|快速检没有锁的现有实例
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         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) {
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

至此,循环依赖总体流程分析结束。下面我们那一个同事询问我的bug分析分析

@Async引发的bug分析

报错代码贴了,就是上面org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean中下面抛出异常的那一段

//如果允许解决循环依赖的标识
if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         //依赖这个bean的所有bean
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            //如果依赖这个bean的bean创建完成,而初始化完成的bean和注入给其他bean不一样,spring就认为是异常了
            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 " +
                  "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
         }
      }
   }
}

那肯定exposedObject == bean不相等了。exposedObject = initializeBean(beanName, exposedObject, mbd);

跟着源码进入到AsyncAnnotationBeanPostProcessor处理的时候,返现返回了一个CgLib代理对象,这时候,剩下的逻辑就一目了然了,实例化后的bean和注入给其他bean不一致,这时候就有问题了,spring认为应该是注入给其他bean的引用应当是一致的,前面放到二级缓存中了,其他bean创建的时候用的就是原始的引用,你初始化后返回了一个代理的引用,spring也就抛出了异常。也算是@Async的一个小bug。

分析完毕。也没啥好总结的,都是很简单的流程。觉得分析的风格还行的可以个一个赞