zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Spring依赖注入(四):Bean的循环依赖是如何产生和解决的?

Spring循环注入依赖 如何 解决 bean 产生
2023-09-14 09:04:52 时间

前言

其实这篇文章才是正主,前面几篇文章:Spring依赖注入(一):字段注入的方式是如何工作的?Sprng依赖注入(二):setter注入是如何工作的?Sprng依赖注入(三):构造方法注入是如何工作的?,都是是铺垫。从前面可以知道,Sping的Bean依赖注入大致有三种方法:字段注入、setter方法注入、构造方法注入,其中前两种与第三种是有明显区别的,这个区别很关键,和bean循环依赖的解决有着重大关系,而通过本篇文章,会和大家分享一下这其中的秘密:1、什么是循环依赖?2、循环依赖是如何解决的?3、有些循环依赖为什么是解决不了的?

什么是循环依赖?

类A依赖类B,类B依赖类A,像这样两个及以上的类彼此依赖形成了一个闭环,这种情况就叫作循环依赖。如下:Student类依赖了Teacher,Teacher类又反过来依赖了Student。

@Component
public class Student {
    private String name = "小明";
    @Autowired
    private Teacher teacher;
}
@Component
public class Teacher {
    private String name = "李老师";
    @Autowired
    private Student student;
}

怎么解决循环依赖?

依赖注入的方式从实现原理上来说,就三种,分别字段注入、setter方法注入、构造方法注入,且这三种方式都用到了反射技术。就循环依赖来说,如果是两个bean之间发生循环依赖,那么根据不同的注入方式进行组合,有9种组合;如果是三个bean发生循环依赖,那么可以使用注入方式的组合方式会更加复杂;以两个bean之间发生循环依赖为例,不同的注入方式组合后,结果如下:

目标bean

引用依赖bean

循环依赖是否可以解决

字段注入

字段注入

可以解决

字段注入

setter方法注入

可以解决

字段注入

构造方法注入

可以解决

setter方法注入

字段注入

可以解决

setter方法注入

setter方法注入

可以解决

setter方法注入

构造方法注入

可以解决

构造方法注入

字段注入

解决不了

构造方法注入

setter方法注入

解决不了

构造方法注入

构造方法注入

解决不了

从结果上分析来看,凡是目标bean,即最先开始实例化的student对象,是构造方法注入依赖,那么不管引用依赖bean对象(teacher)是什么方式注入的,都会解决不了循环依赖而报错。那么Spring的bean依赖注入其实就可以归纳两类:

1、字段注入、setter方法注入;

2、构造方法注入;

其实前面之所以我要用三篇文章来分别详细分析三种依赖注入方式的工作过程,就在于这里:通过对比,找出每种方式的不同点在哪?那么循环依赖能解决的原理?循环依赖不能解决的原因就再清晰不过了。下面分别来看一下:

字段注入和setter方法注入

在没有发生bean的循环依赖情况下,以示例Student、Teacher类而言,字段注入和setter方法注入方式的工作过程大概是这样的:

1、先实例化student;

2、student实例化完成后,把“半成品”的student放入Spring三级缓存中;

3、开始student依赖属性的注入;

4、这时发现依赖属性teacher未实例化,于是开始teacher的实例化、属性注入、初始化;

5、teacher创建完成后,继续student的属性注入,即使用java反射把teacher对象注入到student内,然后student的创建过程结束;

在Student、Teacher之间发生循环依赖的情况下工作流程是什么样的呢?

1、先实例化student;

2、student实例化完成后,把“半成品”的student放入Spring三级缓存中;

3、开始student依赖属性的注入;

4、这时发现依赖属性teacher未实例化,于是开始teacher的实例化、属性注入、初始化;但是开始Teacher的实例化完后,开始属性注入teacher的属性student对象,但是student此时还在等teacher创建完成,正常情况下,这么互相等对方,根本没法玩;然而Spring有三级缓存,缓存的key是beanName,但是value不是一个具体的对象,而用lambda表达式写的接口ObjectFactory,ObjectFactory接口里有一个抽象方法getObject(),这里是什么意思呢?ObjectFactory#getObject()方法被触发的时候,会执行lambda表达式的内容; 而lambda表达式里的getEarlyBeanReference()正是从三级缓存里取出完成实现化、未属性注入的半成品bean的引用;teacher对象拿到半成品的student对象引用,就可以完成teacher的属性注入、初始化;

5、teacher通过使用Spring三级缓存里的半成品student完成创建后,student就可以使用创建好的teacher完成依赖属性注入,即使用java反射把teacher对象注入到student内,然后student的创建过程结束;

构造方法注入

在没有发生bean的循环依赖情况下,以示例Student、Teacher类而言,字段注入和setter方法注入方式的工作过程大概是这样的:

1、调用Student的有参数构造方法开始bean的实例化;

2、Student的有参数构造方法需要注入Teacher,但是Teacher实际未开始实例化;

3、开始去实例化Teacher;

4、Teacher实例化、属性注入、初始化完成后,往Student的有参数构造方法里注入,student的依赖注入完成;

5、student对象会被放入到Spring的三级缓存;

6、student对象其他的依赖属性注入;

7、student对象初始化,然后student创建过程结束;

1、调用Student的有参数构造方法开始bean的实例化;

2、Student的有参数构造方法需要注入Teacher,但是Teacher实际未开始实例化;

3、实例化Teacher完后,进行依赖属性注入时,发现teacher对象又依赖了student对象,而此时student对象还未实例化完成,一级、二级、三级缓存里还空空如也呢,就这样student对象等着teacher创建完成后再用构造方法注入,teacher也等student创建完成后再注入;如图红色部分,这种情况,bean的循环依赖无法解决,原因就在于构造方法注入这种方式是把bean实例化、属性注入合成了一步,没办法使用到Spring的三级缓存;

小结

循环依赖的解决原理

字段注入和setter方法注入,这两种方式下的循环依赖之所以能够解决,在于bean在实例化、属性注入分成了两步,且bean实例化后、未开始属性注入前,提前暴露在了Spring的三级缓存中,如果发生循环依赖,那么可以通过三级缓存提前拿到未实例化完成半成品bean的引用,互相完成bean属性注入后,半成品的bean就变成了一个完整bean。

循环依赖不能解决的原因

构造方法注入,这种方式不能解决的原因,在于bean的实例化、属性注入合成了一步,循环依赖发生时,两个bean都没有实例化完成,且不能提前暴露Spring的三级缓存中,所以就会发生异常;

Spring中bean创建核心逻辑都在AbstractAutowireCapableBeanFactory#doCreateBean(),建议反复研究研究。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
 
   // ------start-----bean实例化-------------------
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   Object bean = instanceWrapper.getWrappedInstance();
   Class<?> beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }
    // ------end-----bean实例化-------------------
   // ------start----bean后置处理器-------------------
   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;
      }
   }
    //------end----bean后置处理器-------------------
    //------start----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
   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));
   }
    //------end----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
   Object exposedObject = bean;
   try {
       //bean的属性注入
      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)) {
            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 " +
                     "'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;
}