Spring的循环依赖

港控/mmm° 2024-03-23 14:54 141阅读 0赞

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或者两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 C,C 又依赖于 A。如下图:

37835a025fe84ec4a9e211f029353dcf.png

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

Spring 中循环依赖场景有:

(1)构造器的循环依赖

(2)field 属性的循环依赖

其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring 采用的是提前暴露对象的方法。

怎么检测是否存在循环依赖

检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

Spring 怎么解决循环依赖

Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。(但是构造器必须是在获取引用之前)。

完整实例 bean,需要通过上面三个步骤:

  1. createBeanInstance 方法: 得到一个实例 bean,但是没有进行属性值的注入

  2. populateBean 方法:就是对 bean 进行属性值注入。

  3. initializeBean 方法:如果配置文件里面有 init 方法,需要执行 init 方法。

可以看出第一步和第二步比较重要,这里面就是解决 bean 依赖的关键。

5bc0a38be1876912eb9438e79640d117.png

b16fcb73a66a2adcb786fe68996ec125.png

我们可以看出进行 createBeanInstance 方法,得到了 bean 实例对象,但是没有属性注入。把没有完全实例化的 bean,放到 addSinletonFactory 方法里面去,这样相当于就是提前暴露 bean,接下来 addSinletonFactory 方法,这里面使用三个 Map 类型的,及三级缓存来解决循环依赖。接下我们看一下 addSingletonFactory 方法实现。

61350feddd4a33a39d7f02403b1061c6.png

c888648670221a7b70b96bd52a1da9f2.png

singleObjects—》单例对象的 cache:一级缓存

earlySingletonObjects —》提前暴光的单例对象的 Cache :二级缓存

singletonFactories —》单例对象工厂的 cache :三级缓存

9ebdecc2e36be517726b8200923847cc.png

首先解释两个参数:

  • isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中,当单例对象没有被初始化完全 (例如 A 定义的构造函数依赖了 B 对象,得先去创建 B 对象,或者在 populatebean 过程中依赖了 B 对象,得先去创建 B 对象,此时 A 处于创建中)
  • allowEarlyReference 是否允许从 singletonFactories 中通过 getObject 拿到对象

在创建 bean 的时候,首先想到的是从 cache 中获取这个单例的 bean,这个缓存就是 singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存 earlySingletonObjects 中获取。如果还是获取不到且允许 singletonFactories 通过 getObject () 获取,就从三级缓存 singletonFactory.getObject ()(三级缓存) 获取,如果获取到了则:从 singletonFactories 中移除,并放入 earlySingletonObjects 中。其实也就是从三级缓存移动到了二级缓存。

Spring 解决循环依赖的诀窍就在于 singletonFactories 这个 cache,这个 cache 中存的是类型为 ObjectFactory,其定义如下

  1. public interface ObjectFactory<T> {
  2. T getObject() throws BeansException;}

在 bean 创建过程中,有两处比较重要的匿名内部类实现了该接口。一处是

  1. new ObjectFactory<Object>() {
  2. @Override public Object getObject() throws BeansException {
  3. try {
  4. return createBean(beanName, mbd, args);
  5. } catch (BeansException ex) {
  6. destroySingleton(beanName);
  7. throw ex;
  8. } }

另一处就是

  1. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  3. synchronized (this.singletonObjects) {
  4. if (!this.singletonObjects.containsKey(beanName)) {
  5. this.singletonFactories.put(beanName, singletonFactory);
  6. this.earlySingletonObjects.remove(beanName);
  7. this.registeredSingletons.add(beanName);
  8. }
  9. }
  10. }

这里就是解决循环依赖的关键,这段代码发生在 createBeanInstance 之后,也就是说单例对象此时已经被创建出来 (调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以 Spring 此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下 “A 的某个 field 或者 setter 依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 依赖了 A 的实例对象” 这种循环依赖的情况。A 首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中,此时进行初始化的第二步,发现自己依赖对象 B,此时就尝试去 get (B),发现 B 还没有被 create,所以走 create 流程,B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get (A),尝试一级缓存 singletonObjects (肯定没有,因为 A 还没初始化完全),尝试二级缓存 earlySingletonObjects(也没有),尝试三级缓存 singletonFactories,由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象 (虽然 A 还没有初始化完全,但是总比没有好呀),B 拿到 A 对象后顺利完成了初始化阶段 1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。此时返回 A 中,A 此时能拿到 B 的对象顺利完成自己的初始化阶段 2、3,最终 A 也完成了初始化,进去了一级缓存 singletonObjects 中,而且更加幸运的是,由于 B 拿到了 A 的对象引用,所以 B 现在 hold 住的 A 对象完成了初始化。

知道了这个原理时候,肯定就知道为啥 Spring 不能解决 “A 的构造方法中依赖了 B 的实例对象,同时 B 的构造方法中依赖了 A 的实例对象” 这类问题了!因为加入 singletonFactories 三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

为啥要三级缓存

必须使用三级缓存吗 两级不行吗 提前暴露时直接放到二级缓存 earlySingletonObjects 里会有什么问题呢?

answer:如果不存在循环依赖,那么就没必要提早曝光放到 earlySingletonObjects 里。如果像你说的只有两级缓存,那简单的 A 依赖 B,A 在第一步初始化未注入 field 的时候就放到了 earlySingletonObjects,显然不符合常理,只能说放到三级缓存中预防循环依赖的产生。singletonFactories 并没有提早曝光的意思,只是为了缓存,earlySingletonObjects 才为了提早曝光,所以才要三级的。

发表评论

表情:
评论列表 (有 0 条评论,141人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring循环依赖

    在Spring 中循环依赖是一个问题, 为什么? 因为,在Spring中,一个对象并不是简单new出来的, 而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期,

    相关 Spring循环依赖

    什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或者两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 C,C 又依赖于 A。如下图

    相关 spring循环依赖

    Bean的生命周期 这里不会对Bean的生命周期进行详细的描述,只描述一下大概的过程。 Bean的生命周期指的就是:在Spring中,Bean是如何生成的? > 被Sp