Spring 循环依赖详解:原理剖析与实战解决方案

153 阅读7分钟

关注公众号 不爱总结的麦穗 将不定期推送技术好文

  在 Spring 框架中,三级缓存主要应用于解决 循环依赖 问题,特别是在 Spring IoC 容器初始化时创建 Bean 的过程中。三级缓存可以确保在复杂的 Bean 依赖情况下,Spring 可以通过懒加载和代理的方式避免循环依赖。

什么是循环依赖?

  在 Spring 框架中循环依赖是指两个或两个以上的bean互相引用对方,导致它们无法独立创建或初始化。

  某些业务在实现的时候可能就是需要bean A依赖bean B,同时bean B又依赖bean A,就形成了循环依赖。Spring存在循环依赖并不会直接报错,只有Spring无法自动解决的时候才会报错。

Spring 如何检测循环依赖

  Spring 使用一个singletonCurrentlyInCreationSet<String> 集合来跟踪当前正在创建的单例 Bean,它记录了哪些 Bean 当前正在创建过程中。

private final Set<String> singletonsCurrentlyInCreation =
      Collections.newSetFromMap(new ConcurrentHashMap<>(16));
  • 当 Spring 开始创建一个 Bean(例如 A),它会将 A 的名字添加到 singletonCurrentlyInCreation 集合中。

  • 当需要注入其他 Bean(例如 B)时,Spring 会检查 singletonCurrentlyInCreation 集合,如果发现 B 的名字已经在集合中,则意味着已经在创建 B,从而发现循环依赖。

Spring 三级缓存

  循环依赖检测到后,Spring 会通过三级缓存机制来解决(所谓三级缓存就是三个Map)。

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • singletonObjects,一级缓存,存放所有创建好了的单例Bean
  • earlySingletonObjects二级缓存,存放所有完成实例化但还未进行属性注入及初始化的Bean
  • singletonFactories,三级缓存,存放单例Bean的工厂

三级缓存原理

  Spring容器的启动是从refresh()方法开始的,在refresh()方法中会调用finishBeanFactoryInitialization()方法,该方法会创建出所有非懒加载的bean。

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

   // 实例化Bean
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   final Object bean = instanceWrapper.getWrappedInstance();
  
   // 部分代码省略...
   
   // 实例化的bean(还没初始化的类(当bean为单例 && 容器配置允许循环依赖 && bean正在创建))包装为ObjectFactory对象,放入到三级缓存singletonFactories中
   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");
      }
      // 放入到三级缓存singletonFactories中
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

  
   Object exposedObject = bean;
   try {
      // populateBean方法用来装配bean(依赖注入),当发现依赖关系之后会去容器中查找依赖的bean
      populateBean(beanName, mbd, instanceWrapper);
      
      // bean功能的增强(aware接口、bean后置处理器、初始化方法),AOP的代理对象在这里生成
      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) {
      // 从缓存中获取bean,这里注意第二参数是false 
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         
         // 部分代码省略 ...
      }
   }

   return exposedObject;
}

再来看看getSingleton()方法是怎样从缓存中获取bean?

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) {
           // 当allowEarlyReference为true时从三级缓存中查找
           ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
           if (singletonFactory != null) {
              singletonObject = singletonFactory.getObject();
              // 从三级缓存中找到之后会移入到二级缓存
              this.earlySingletonObjects.put(beanName, singletonObject);
              this.singletonFactories.remove(beanName);
           }
        }
     }
  }
  return singletonObject;
}

到这里,spring 如何解决循环引用的,答案就揭晓了:

  当AB存在相互依赖时,Spring首先创建A,实例化完成之后把A包装为ObjectFactory对象放入到三级缓存singletonFactories中。然后对A进行依赖注入,此时发现A依赖B,spring会去容器中查找B,发现B还没有创建,所以先去创建B,同样实例化结束之后将B包装为ObjectFactory对象放入到三级缓存中;然后对B进行依赖注入,发现B依赖A,spring会去容器中查找A,通过getSingleton()方法从缓存中查找,会在第三级缓存中找到A的ObjectFactory对象,通过ObjectFactory对象调用getObject方法获取到A,并将A放入到二级缓存earlySingletonObjects中,顺便在三级缓存中把ObjectFactory对象移除。尽管A还没装配结束,但B已经获取到了A,B的初始化完成了,B从三级缓存移入到一级缓存。然后回过头来继续初始化A,将B注入A,A也初始化完成,A从二级缓存移入到一级缓存。

  乍一看,好像不需要三级缓存就能解决循环依赖的问题了,两级完全可以了。为什么会需要三级缓存呢?我们别忘了Spring还有一个很重要的特性彻底搞懂Spring AOP

还是上面AB相互依赖的例子,假设我们对A进行了AOP,Spring会怎么解决呢?

  还是之前的场景,当B创建时,发现其依赖于A,但是B需要的是A的代理对象。B会在第三级缓存中找到A的ObjectFactory对象,通过ObjectFactory对象调用getObject方法获取到A的代理对象,并将A的代理对象放入到二级缓存earlySingletonObjects中

三级缓存存放的是ObjectFactory对象。

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference()方法会判断当前bean是否需要进行AOP处理

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   // 判断当前bean是否需要进行AOP处理
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}


public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    // earlyProxyReferences value值是原来的bean
    this.earlyProxyReferences.put(cacheKey, bean);
    // 生成代理对象
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}

  当B的初始化完成,回过头来继续初始化A,将B注入A后。A执行到initializeBean()方法时,因为A进行了AOP会去生成代理对象,但是呢A的代理对象已经生成过了,不能再生成一个代理对象。

  // populateBean方法用来装配bean(依赖注入),当发现依赖关系之后会去容器中查找依赖的bean
  populateBean(beanName, mbd, instanceWrapper);
      
   // bean功能的增强(aware接口、bean后置处理器、初始化方法),AOP的代理对象在这里生成
   exposedObject = initializeBean(beanName, exposedObject, mbd);

  A的代理对象还是要从缓存中获取。此时一级缓存中还没有,三级缓存中存储的仍然是原本的bean,所以只能从二级缓存中来获取到代理后的bean。这就是需要三级缓存的原因。

     if (earlySingletonExposure) {
      // 从缓存中获取bean,这里注意第二参数是false 
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         
         // 部分代码省略 ...
      }
   }

总结

   Spring 中实现依赖注入的常见方式有以下 3 种

  • 属性注入(Field Injection)

image.png

  • Setter 注入(Setter Injection)

image.png

  • 构造方法注入(Constructor Injection)

image.png

Spring可以自动解决一部分循环依赖的情况:

依赖场景注入方式是否自动解决
AB相互依赖均采用setter方法注入
AB相互依赖均采用属性注入
AB相互依赖均采用构造器注入
AB相互依赖A中注入B的方式为setter方法,B中注入A的方式为构造器
AB相互依赖B中注入A的方式为setter方法,A中注入B的方式为构造器

(大家可以思考一下为什么?想通了,那就恭喜你)