写点什么

Spring 中的循环依赖是怎么个事?

  • 2024-12-17
    福建
  • 本文字数:3175 字

    阅读完需:约 10 分钟

Spring中的循环依赖是怎么个事?

首先,有两种 Bean 注入的方式:构造器注入和属性注入。

  1. 对于构造器注入的循环依赖,Spring 处理不了,会直接抛出 BeanCurrentlylnCreationException 异常。

  2. 对于属性注入的循环依赖

    单例模式下,是通过三级缓存处理来循环依赖的。

    非单例对象的循环依赖,则无法处理。

单例模式下的属性依赖

先来看下这三级缓存

/** Cache of singleton objects: bean name --> bean instance */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** Cache of early singleton objects: bean name --> bean instance */private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
复制代码
  • 第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象

  • 第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象

  • 第三层缓存(singletonFactories): 单例工厂的缓存

如下是获取单例中:

getSingleton 在 doGetBean 方法中调用

protected Object getSingleton(String beanName, boolean allowEarlyReference) {  // Spring首先从singletonObjects(一级缓存)中尝试获取  Object singletonObject = this.singletonObjects.get(beanName);  // 若是获取不到而且对象在建立中,则尝试从earlySingletonObjects(二级缓存)中获取  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) {            //若是仍是获取不到而且容许从singletonFactories经过getObject获取,则经过singletonFactory.getObject()(三级缓存)获取              singletonObject = singletonFactory.getObject();              //若是获取到了则将singletonObject放入到earlySingletonObjects,也就是将三级缓存提高到二级缓存中              this.earlySingletonObjects.put(beanName, singletonObject);              this.singletonFactories.remove(beanName);          }        }    }  }  return (singletonObject != NULL_OBJECT ? singletonObject : null);}
复制代码

补充一些方法和参数

  • isSingletonCurrentlyInCreation():判断当前单例 bean 是否正在建立中,也就是没有初始化完成(好比 A 的构造器依赖了 B 对象因此得先去建立 B 对象, 或则在 A 的 populateBean 过程当中依赖了 B 对象,得先去建立 B 对象,这时的 A 就是处于建立中的状态。)

  • allowEarlyReference :是否容许从 singletonFactories 中经过 getObject 拿到对象

分析 getSingleton()的整个过程,Spring 首先从一级缓存 singletonObjects 中获取。若是获取不到,而且对象正在建立中,就再从二级缓存 earlySingletonObjects 中获取。若是仍是获取不到且容许 singletonFactories 经过 getObject()获取,就从三级缓存 singletonFactory.getObject()(三级缓存)获取,若是获取到了则从三级缓存移动到了二级缓存。

从上面三级缓存的分析,咱们能够知道,Spring 解决循环依赖的诀窍就在于 singletonFactories 这个三级 cache。这个 cache 的类型是 ObjectFactory,定义以下:

public interface ObjectFactory<T> {    T getObject() throws BeansException;}
复制代码

在 bean 建立过程当中,有两处比较重要的匿名内部类实现了该接口。一处是 Spring 利用其建立 bean 的时候,另外一处就是:

addSingletonFactory(beanName, new ObjectFactory<Object>() {   @Override   public Object getObject() throws BeansException {      return getEarlyBeanReference(beanName, mbd, bean);   }});
复制代码

此处就是解决循环依赖的关键,这段代码发生在 createBeanInstance 以后,也就是说单例对象此时已经被建立出来的。这个对象已经被生产出来了,虽然还不完美(尚未进行初始化的第二步和第三步),可是已经能被人认出来了(根据对象引用能定位到堆中的对象),因此 Spring 此时将这个对象提早曝光出来让你们认识,让你们使用。

好比“A 对象 setter 依赖 B 对象,B 对象 setter 依赖 A 对象”,A 首先完成了初始化的第一步,而且将本身提早曝光到 singletonFactories 中,此时进行初始化的第二步,发现本身依赖对象 B,此时就尝试去 get(B),发现 B 尚未被 create,因此走 create 流程,B 在初始化第一步的时候发现本身依赖了对象 A,因而尝试 get(A),尝试一级缓存 singletonObjects(确定没有,由于 A 还没初始化彻底),尝试二级缓存 earlySingletonObjects(也没有),尝试三级缓存 singletonFactories,因为 A 经过 ObjectFactory 将本身提早曝光了,因此 B 可以经过 ObjectFactory.getObject 拿到 A 对象(半成品),B 拿到 A 对象后顺利完成了初始化阶段一、二、三,彻底初始化以后将本身放入到一级缓存 singletonObjects 中。此时返回 A 中,A 此时能拿到 B 的对象顺利完成本身的初始化阶段二、三,最终 A 也完成了初始化,进去了一级缓存 singletonObjects 中,并且更加幸运的是,因为 B 拿到了 A 的对象引用,因此 B 如今 hold 住的 A 对象完成了初始化。

为什么不能解决非单例属性之外的循环依赖?

  1. 为何不能解决构造器的循环依赖?

    构造器注入形成的循环依赖: 也就是 beanB 需要在 beanA 的构造函数中完成初始化,beanA 也需要在 beanB 的构造函数中完成初始化,这种情况的结果就是两个 bean 都不能完成初始化,循环依赖难以解决。

    Spring 解决循环依赖主要是依赖三级缓存,但是的在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的 Bean,因此不能解决。

  2. 为什么不能解决 prototype 作用域循环依赖?

    这种循环依赖同样无法解决,因为 spring 不会缓存‘prototype’作用域的 bean,而 spring 中循环依赖的解决正是通过缓存来实现的

  3. 为什么不能解决多例的循环依赖?

    多实例 Bean 是每次调用一次 getBean 都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。

其它循环依赖如何解决

这类循环依赖问题解决方法很多,主要有:

  • 使用 @Lazy 注解,延迟加载

    构造器循环依赖这类循环依赖问题可以通过使用 @Lazy 注解解决

  • 使用 @DependsOn 注解,指定加载先后关系

    使用 @DependsOn 产生的循环依赖:这类循环依赖问题要找到 @DependsOn 注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

  • 修改文件名称,改变循环依赖类的加载顺序

  • 多例循环依赖这类循环依赖问题可以通过把 bean 改成单例的解决。

作为程序员,持续学习和充电非常重要,作为开发者,我们需要保持好奇心和学习热情,不断探索新的技术,只有这样,我们才能在这个快速发展的时代中立于不败之地。低代码也是一个值得我们深入探索的领域,让我们拭目以待,它将给前端世界带来怎样的变革,推荐一个低代码工具。

应用地址:https://www.jnpfsoft.com

开发语言:Java/.net

这是一个基于 Flowable 引擎(支持 java、.NET),已支持 MySQL、SqlServer、Oracle、PostgreSQL、DM(达梦)、 KingbaseES(人大金仓)6 个数据库,支持私有化部署,前后端封装了上千个常用类,方便扩展,框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用。

至少包含表单建模、流程设计、报表可视化、代码生成器、系统管理、前端 UI 等组件,这种情况下我们避免了重复造轮子,已内置大量的成熟组件,选择合适的组件进行集成或二次开发复杂功能,即可自主开发一个属于自己的应用系统。

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Spring中的循环依赖是怎么个事?_伤感汤姆布利柏_InfoQ写作社区