Spring循环依赖

本文分析了Spring2.6之后对循环依赖的处理策略,特别关注了构造注入情况下禁止循环依赖的原因,以及Spring如何通过三级缓存机制允许特定类型的循环依赖。作者详细解释了Spring如何在创建过程中利用早期曝光对象和代理对象来确保安全和扩展性。
摘要由CSDN通过智能技术生成


前言:在 Spring 2.6 之后,无论什么形式的循环依赖默认都是禁止的,需要手动在配置文件开启:spring.main.allow-circular-references=true

结论

顾名思义,最简单的循环依赖就是:

@Component
public class AServiceImpl implements AService {
  @Resource
  private BService bs;
}

@Component
public class BServiceImpl implements BService {
  @Resource
  private AService as;
}

而虽然可以循环依赖,但是也有不能循环依赖的特例

根据A先被创建、然后再到B被创建的顺序,能不能循环依赖跟注入方式有关,直接放结论:

原理

为什么有的可以循环依赖,有的不可以,可以的那些循环依赖Spring是如何做到让他可以循环依赖的,不可以循环依赖的又为什么Spring无法做到循环依赖?

禁止的循环依赖

首先看不能循环依赖的两种情况:

  • AB均为构造注入:很好理解,new A需要B实例先存在,new B需要A实例先存在,所以不能这么循环依赖
  • A注入B为构造,B注入A为setter:注意到A先要被创建,因此此时没有B的实例,无法new A

允许的循环依赖

再说说其他三种Spring如何实现循环依赖:使用三级缓存来实现

Bean是由BeanFactory管理的,在DefaultSingletonBeanRegistry中有着三级缓存:

/** 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);
  1. 一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
  2. 二级缓存 : Map<String,Object> earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例
  3. 三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。

我们先不一头扎进三级缓存,先看看只有一级缓存会怎么样,就是这个:

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

singletonObjects这里保存着缓存的bean,也就是BeanFactory.getBean获取bean的地方。先来回顾下bean的创建大致过程:

  • 实例化bean(createBeanInstance)
  • 为bean注入依赖(populateBean)
  • 初始化bean(initializeBean)

一级缓存

回到上面的A和B循环依赖创建过程,如果只有这个一级缓存,循环依赖创建过程如下:

  1. 实例化A,并放入一级缓存,便于下面注入B中,注意此时缓存中的A是刚实例化的,还没注入依赖和初始化
  2. 为A注入依赖(即B),发现还没创建B,去创建B
  3. 实例化B,并放入一级缓存
  4. 为B注入依赖(即A),发现一级缓存中有A,并注入B中
  5. 初始化B
  6. 继续将缓存中的B注入A
  7. 初始化A

OK,看起来完全没问题。但是又有了一个新问题,这个缓存中的bean可能有已经创建完成的、正在创建中还没有注入依赖的,它们都掺杂在一起,我们如何保证Map里面的所有对象是完整的呢?一层缓存很显然不符合设计规范,也缺乏安全性与扩展性。

二级缓存

我们希望的是,明确已经构建完成的的Bean被放入到一个缓存中,创建中的bean在另外一个缓存中。那就再增加一层缓存呗,用于保存正在创建的bean,因此引出二级缓存

二级缓存也很简单,只需要创建bean的「实例化bean,并放入一级缓存」,改为「实例化bean,并放入二级缓存」,并且在「初始化bean」之后再增加一个「将bean从二级缓存移到一级缓存」,此时过程变为:

  1. 实例化A,并放入二缓存,便于下面注入B中,注意此时缓存中的A是刚实例化的,还没注入依赖和初始化
  2. 为A注入依赖(即B),发现还没创建B,去创建B
  3. 实例化B,并放入二级缓存
  4. 为B注入依赖(即A),发现二级缓存中有A,并注入B中
  5. 初始化B,并将其从二级缓存移到一级缓存
  6. 继续将缓存中的B注入A
  7. 初始化A,并将其从二级缓存移到一级缓存

但是我们注意到,「初始化bean」这一步返回的有可能不是实例化出来的bean对象,而是代理对象,具体看一下初始化bean的实现:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
  // ...
  
  Object wrappedBean = bean;
  if (mbd == null || !mbd.isSynthetic()) {
   // 这里会遍历所有注册了的BeanPostProcessor,并调用他们的postProcessBeforeInitialization方法
   // 返回的可能是代理对象,或者更通俗的说不是原来实例化的对象,而是原来对象的wrapper/proxy
   wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  }
	// 调用一些初始化方法
  try {
   invokeInitMethods(beanName, wrappedBean, mbd);
  }
  catch (Throwable ex) {
   throw new BeanCreationException(
     (mbd != null ? mbd.getResourceDescription() : null),
     beanName, "Invocation of init method failed", ex);
  }
  if (mbd == null || !mbd.isSynthetic()) {
   // 这里会遍历所有注册了的BeanPostProcessor,并调用他们的postProcessAfterInitialization方法
   // 返回的可能是代理对象,或者更通俗的说不是原来实例化的对象,而是原来对象的wrapper/proxy
   wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  }

  return wrappedBean;
}

如果程序中使用了比如AOP功能的话,产生了A的代理类,那么第4步注入B的那个A(普通的A),和7步初始化完毕的那个A(代理对象),就不是同一个对象了!

那么能不能在实例化之后,注入依赖之前,就把代理对象生成出来呢,这样将其注入B的就是代理对象了?

但是代理是通过BeanPostProcessor得到的,而且根据BeanPostProcessor的使用规范:postProcessBeforeInitialization用于设置bean的一些属性,postProcessAfterInitialization用于为bean生成代理对象。其中postProcessAfterInitialization按个人理解是在属性设置完毕后才生成代理对象,因此没办法让对象在注入依赖(设置属性)之前就生成代理对象。

那怎么办?其实还是类似「注入依赖之前就把代理对象生成出来」的做法,只不过是另一种形式「注入循环依赖的时候把代理对象生成出来」。

至此,引出三级缓存

三级缓存

三级缓存保存 bean 创建工厂,这个工厂非常简单,直接看源码(省略部分代码、try-catch等):

// 创建bean的全套核心流程都在这doCreateBean
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
   throws BeanCreationException {

  // 省略实例化bean以及部分无关代码,这里变量bean就是一个普通的、刚被实例化出来的bean
  bean = ...;

  // 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");
   }
   // 将bean工厂加入三级缓存,等下有大作用
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  Object exposedObject = bean;
  // 给bean注入依赖
  populateBean(beanName, mbd, instanceWrapper);
  // 初始化bean
  exposedObject = initializeBean(beanName, exposedObject, mbd);

  if (earlySingletonExposure) {
   // 从缓存获取bean
   Object earlySingletonReference = getSingleton(beanName, false);
   // 如果从缓存中获取到了bean
   if (earlySingletonReference != null) {
    // 检查初始化bean后,有没有再被代理过
    if (exposedObject == bean) {
     exposedObject = earlySingletonReference;
    }
    // 继续以A为例,比如方法标注了@Aysnc注解,exposedObject此时候就是一个代理对象,因此就会进到这里来
    // 检查A的循环依赖对象,如果他们还没最终创建好的话,就从缓存移除他们,让他们重新依赖A
    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;
}

一句话概括,加入三级缓存前,bean已经实例化出来了,这个工厂只是“加工厂”,它调用了getEarlyBeanReference,目的是生成bean的代理对象(如果有的话)。

比如A依赖B,当创建B的时候,由于循环引用B又去缓存找A,从三级缓存找到这个工厂,并调用他,相当于调用getEarlyBeanReference,生成A的代理对象,这样就实现了上面说的「注入循环依赖(B)的时候把代理对象(A)生成出来」,那么此时注入B的就是我们想要的A的代理对象。看一下getEarlyBeanReference:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  Object exposedObject = bean;
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
   // 遍历BeanPostProcessor中的SmartInstantiationAwareBeanPostProcessor,生成代理
   for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
   }
  }
  return exposedObject;
}

但是接下来也有问题,我们继续上面过程,B注入A代理对象并初始化完成后,然后A注入B也完成了,接着初始化A,之前说了在「初始化A」这步可能生成的是A的代理对象,即遍历BeanPostProcessor生成代理。

注意这里的区别:

  • getEarlyBeanReference:遍历BeanPostProcessor中的SmartInstantiationAwareBeanPostProcessor,生成代理
  • initializeBean:遍历BeanPostProcessor,生成代理

这样一来,生成的代理对象可能又与B依赖的那个代理对象不一样了,因此下面的代码中会尝试移除B的缓存让其重新依赖A,否则抛出异常。

总结

回过头来看看为什么要第三级缓存,而且为什么是工厂,而不是直接调用getEarlyBeanReference得到的代理对象。这是因为如果没有循环依赖的话,工厂根本不会被调用,没必要提早生成这个代理对象,只需要按原来的流程,在initializeBean正常生成代理对象即可。

然后为什么需要第二级缓存,之前已经说了,存放还没完全创建好的bean呗。而且从另一个角度来说,第二级缓存也能缓存第三级缓存生成得到的对象,比如出现「A依赖B和C,B和C也依赖A」的情况,那么往B和C注入A的时候,相当于调用两次工厂,生成了两个不同的A对象,因此第二级缓存用于缓存第三级缓存生成得到的对象。

参考链接

https://zhuanlan.zhihu.com/p/157314153

https://www.cnblogs.com/Courage129/p/14494680.html

https://zhuanlan.zhihu.com/p/496273636

https://javabetter.cn/sidebar/sanfene/spring.html#_16-那-spring-怎么解决循环依赖的呢

https://www.51cto.com/article/747437.html

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring循环依赖指的是在Spring中,多个Bean之间存在相互依赖的情况。具体来说,当一个Bean A依赖于另一个Bean B,同时Bean B也依赖于Bean A时,就形成了循环依赖。这种情况下,Spring需要解决Bean的创建和依赖注入的顺序问题。 在Spring中,循环依赖问题是由于Bean的生命周期所引起的。Spring的Bean生命周期包括了Bean的实例化、属性注入、初始化以及销毁等过程。当出现循环依赖时,Spring会通过使用“提前暴露”的方式来解决这个问题。 具体来说,当Spring创建Bean A时,发现它依赖于Bean B,于是会创建一个A的半成品对象,并将其暂时放入一个缓存中。然后,Spring会继续创建Bean B,并将其注入到A的属性中。接着,Spring会继续完成B的创建,并将其放入缓存中。最后,Spring会将A的半成品对象交给B进行依赖注入,完成A的创建,并将其从缓存中移除。 需要注意的是,Spring循环依赖有一定的限制条件。例如,如果Bean A和Bean B都是单例模式,那么它们之间的循环依赖是无法解决的。因为单例模式下,Bean的创建和依赖注入是同时进行的,无法通过缓存来解决循环依赖。在这种情况下,程序员需要手动调整Bean的依赖关系或使用其他解决方案来避免循环依赖的问题。 综上所述,Spring循环依赖是指在Spring中多个Bean之间存在相互依赖的情况。Spring通过使用缓存和提前暴露的方式来解决循环依赖问题,但在某些情况下有一定的限制条件需要注意。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值