这可能是最全面的Spring循环依赖分析!

良心公众号

关注不迷路

菜鸡之所以讨论这个问题,主要有两方面的原因:一是因为,这是一道BAT级别的经典面试题;二是因为,工作中经常会涉及Spring循环依赖的相关问题。因此,特将该问题作如下整理,希望能给看到这篇文章的小伙伴或多或少的帮助。

当你在面试过程中和面试官聊到了Spring,那么多半会被问及Spring 如何解决循环依赖的问题。这是一道很能显水平的问题,这道面试题和谈谈HashMap的底层实现的问题很类似。因为这两道面试题都是答个七七八八容易,答得完整甚至完美很难,因为涉及的细节太多,除非吃透源码,否则很容易翻车。

典型的解决问题需要分三步走,是什么,为什么,怎么做。对应到本文就是:什么是循环依赖?为什么会产生循环依赖?怎么解决(或避免)循环依赖?

  • 什么是循环依赖?

    循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。

    以上是循环依赖的基本概念。具体到Spring中,循环依赖的问题按照bean的注入方式不同,可以分为构造器注入的循环依赖,setter注入的循环依赖,注解注入的循环依赖。

    为什么要分这三种呢?因为Spring本身无法解决构造器注入引起的循环依赖,也无法解决setter注入和注解注入的原型(@prototype)bean的循环依赖。而仅仅能解决setter注入和注解注入的单例bean的循环依赖。而在实际开发过程中,我们用得最多的就是单例bean。因此,不严格地说,Spring解决了大多数情况下的循环依赖。

    我们为什么要讨论循环依赖?或者说面试官为什么喜欢问循环依赖?因为这涉及到了Spring bean的创建过程(原理层面),而且还很可能在实践过程中遇到相关的问题,或者说知道如何避免一些坑(实践层面)。

  • 为什么会产生循环依赖?

    随着业务的日渐复杂,循环依赖是很难彻底避免的。对于循环依赖的问题,菜鸡本着谨慎但又不过于担心,出现错误能够马上定位到问题的根源的态度,来看待循环依赖。

    上文已经说到,Spring自身解决了大多数情况下的循环依赖,但这并不能说明该范围里的循环依赖都是合理的。有些循环依赖是可以通过良好的设计去避免的,这要取决于系统的模块划分,分层设计等一系列架构上的因素,在此不再展开。

  • 怎么解决(或避免)循环依赖?

    终于到了重头戏了!机智的小伙伴遇到问题的时候肯定更想知道该如何解决。且随菜鸡来看。

    上文已经根据Spring bean的注入方式进行了分类,在此,我们依然沿用该分类,不同应用场景的问题有不同的处理方式。但在这里,我们先看一下,Spring是如何解决setter注入单例bean的循环依赖的,然后再据此看为什么构造器注入和注入原型bean的情况不能解决。

    Spring通过三级缓存来解决单例bean的循环依赖。

    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    
    
      /** 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 HashMap<>(16);
      
      ...
            
    }
    

    可以看到,三级缓存的底层数据结构就是三个Map。第一级缓存是单例缓存池singletonObjects,第二级缓存是提前暴露的对象缓存earlySingletonObjects,第三级缓存是单例对象工厂缓存singletonFactories。所谓的提前暴露的对象,可以简单的理解为声明了一个对象,但是并没有进行初始化。

    接下来我们来看一下在创建单例bean的过程中,三级缓存是如何起作用的。首先,创建bean的过程想必小伙伴们已经有所了解,首先通过调用getBean方法,调用doGetBean方法。其部分源码如下:

    public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
      
      ...
        
      @Override
      public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return doGetBean(name, requiredType, null, false);
      }
      
      ...
    
    
      protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
    
        final String beanName = transformedBeanName(name);
        Object bean;
    
    
        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
    
    
        ...    
        
        return (T) bean;
      }     
    }
    

    在这个过程中,在doGetBean方法中,有一步是调用getSingleton方法,我们来剖析一下这部分源码。

    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    
    
      ...
    
    
      @Override
      @Nullable
      public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
      }
    
    
      @Nullable
      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;
      }
    
    
      ...
         
    }
    

    通过源码可以看出,该方法首先从一级缓存singletonObjects中获取bean的实例,如果获取不到,它会判断该对象是否正在创建当中,如果是的话,它就会从二级缓存earlySingletonObjects中获取,如果获取不到,它会判断是否允许从单例工厂对象缓存中获取对象,如果是的话,它就会从三级缓存singletonFactories中获取,然后将缓存级别提升至二级缓存。

    在这之后,判断dependsOn数组是否满足要求。这个数组的内容是通过@DependsOn注解来指定的依赖关系,我们来看一下源码。

    public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    
    
      ...
    
    
      protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
    
        ...
        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            checkMergedBeanDefinition(mbd, beanName, args);
    
    
            // Guarantee initialization of beans that the current bean depends on.
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
              for (String dep : dependsOn) {
                if (isDependent(beanName, dep)) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                      "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                }
                registerDependentBean(dep, beanName);
                try {
                  getBean(dep);
                }
                catch (NoSuchBeanDefinitionException ex) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                      "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                }
              }
            }
    
    
            ...        
    
    
        }
    
    
        ...
      }
    
    
      ...
          
    }
    

    这里的逻辑是指,如果@DependsOn注解指定的依赖存在循环依赖,在doGetBean阶段会直接抛出BeanCreationException异常。也就是说,@DependsOn注解指定的依赖不允许存在循环依赖。

    接下来,就是尝试createBean了,我们来看一下createBean的源码,看看这个方法里做了什么。

    public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    
    
      ...
    
    
      @Override
      protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
          throws BeanCreationException {
    
    
        if (logger.isTraceEnabled()) {
          logger.trace("Creating instance of bean '" + beanName + "'");
        }
        RootBeanDefinition mbdToUse = mbd;
    
    
        // Make sure bean class is actually resolved at this point, and
        // clone the bean definition in case of a dynamically resolved Class
        // which cannot be stored in the shared merged bean definition.
        Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
        if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
          mbdToUse = new RootBeanDefinition(mbd);
          mbdToUse.setBeanClass(resolvedClass);
        }
    
    
        // Prepare method overrides.
        try {
          mbdToUse.prepareMethodOverrides();
        }
        catch (BeanDefinitionValidationException ex) {
          throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
              beanName, "Validation of method overrides failed", ex);
        }
    
    
        try {
          // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
          Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
          if (bean != null) {
            return bean;
          }
        }
        catch (Throwable ex) {
          throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
              "BeanPostProcessor before instantiation of bean failed", ex);
        }
    
    
        try {
          Object beanInstance = doCreateBean(beanName, mbdToUse, args);
          if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
          }
          return beanInstance;
        }
        catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
          // A previously detected exception with proper bean creation context already,
          // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
          throw ex;
        }
        catch (Throwable ex) {
          throw new BeanCreationException(
              mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
        }
      }
      
      ...
      
    }
    

    我们可以看到,在createBean方法中,有一步调用resolveBeforeInstantiation方法。它将尚未被实例化的Bean声明信息放进缓存,在代理目标对象的时候,可以直接在缓存中获取。

    之后就是真正的bean的创建动作,调用doCreateBean方法

    public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    
    
      ...
       
      protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
          throws BeanCreationException {
    
    
        // Instantiate the 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();
        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.
        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);
        }
        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)) {
              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.");
              }
            }
          }
        }
    
    
        // Register bean as disposable.
        try {
          registerDisposableBeanIfNecessary(beanName, bean, mbd);
        }
        catch (BeanDefinitionValidationException ex) {
          throw new BeanCreationException(
              mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
        }
    
    
        return exposedObject;
      }
    
    
      ...
        
    }    
    

    欣赏着这迷人的代码,让人似乎忘了这篇文章在干嘛。没错,是在讨论循环依赖!

    我们看到,在doCreateBean方法中调用了createBeanInstance方法去创建Bean的实例。

    如果创建成功,就通过调用addSingletonFactory方法把该对象放入三级缓存中,并将对象从二级缓存中移除。

    然后通过调用populateBean方法给对象的属性赋值。

    至此,我们已经了解了单例bean的创建过程,脑海中模拟一下两个相互依赖的单例对象的创建过程,也就相应地清楚了Spring是如何通过三级缓存解决单例bean的循环依赖问题的。

    那么为何构造器注入的循环依赖和原型bean的循环依赖无法通过三级缓存解决呢?

    在createBeanInstance方法中在创建Bean的实例之前,会检测构造器的依赖关系。比如说,如果在创建A对象的时候,发现了A的构造器里依赖了B,就会重新走getBean的这个流程,当再走到这里的时候,又发现B的构造器里依赖了A,此时就会抛出异常。

    之所以会抛出异常,还要从最初的getBean方法入手分析,在getBean方法中,会尝试依次从一级缓存、二级缓存和三级缓存中获取bean,但此时bean尚未被创建好,也就是说缓存中不存在这个bean,所以在缓存中拿不到B对象。同理,也拿不到A对象。于是产生了死循环。

    这就是Spring三级缓存不能解决构造器循环依赖的原因。

    接下来,我们再看,原型bean的循环依赖为什么不能解决。细心的小伙伴已经发现了,在doGetBean方法中,有这样一段逻辑:

    // Fail if we're already creating this bean instance:
    // We're assumably within a circular reference.
    if (isPrototypeCurrentlyInCreation(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
    }
    


    也就是说,如果该原型bean正在被创建,那么会直接抛出异常。

    到这里三种情况都已经说明了,但菜鸡最近还遇到了一个坑,与上面几种不尽相同。菜鸡在采用注解进行单例bean注入的时候,发现Spring报出了循环依赖的错误。菜鸡一度觉得很神奇,经过一番排查,发现在产生循环依赖的类中,有些方法使用了@Transactional注解。相对应的BeanPostProcessor中会生成相应的代理对象,在doCreateBean方法中有一个if判断。我们看一下源代码。

    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.");
        }
      }
    }
    

    由于代理对象和之前的对象不是同一个对象,就会走入else if的逻辑,从而抛出BeanCurrentlyInCreationException异常。与之类似的还有@Async注解,在使用这些注解的时候,要警惕循环依赖的问题。

至此,菜鸡对于循环依赖的分析已经结束了,是不是觉得阅读源码很有收获,尤其是带着问题阅读源码!

本篇文章就到这里了,由于菜鸡水平有限,难免出现谬误,望大佬们不吝赐教,同时也欢迎有问题的小伙伴留言,私信,一起讨论技术,一起成长!

学习 | 工作 | 分享

????长按关注“有理想的菜鸡

只有你想不到,没有你学不到

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值