Spring循环依赖

578 阅读6分钟

说到 Spring 循环依赖,大家应该并不陌生吧。今天就简单对这个问题进行研究。

什么是 Spring 循环依赖

创建A 实例中需要依赖 B,而 B 实例创建又依赖 A 实例,形成一个闭环,这就是 Spring 中的循环依赖。

循环依赖的检测与解决

Spring 循环依赖有几种?

原型的循环依赖【无法解决】
单例 bean 的循环依赖 - 构造参数依赖产生【无法解决】
单例 bean 的循环依赖 - setter 产生依赖 【可以解决】

循环依赖检测过程

刚刚我们了解了,Spring 循环依赖中原型循环依赖和单例构造参数依赖是无法解决的。虽然无法解决,但是我们还是要了解一下 Spring 是如何检测出来的。接下来就开始分析源码了。 首先,我们要从 refresh#finishBeanFactoryInitialization 开始,因为这里实例化所有非原型的 bean。然后又调用了 beanFactory.preInstantiateSingletons(); 接下来我们介绍完前提开始真正的一步一步跟源码了。

1. 首先获取合并后的 BeanDefinition 信息

接下来通过判断 bd,推断出当前 bean 是否是抽象的,是否是单例的,是否是懒加载的。如果不是抽象的,是单例的但不是懒加载的,那么就继续往下走,调用 getBean#doGetBean 方法。

2. 到缓存中去查询,因为当前对象未初始化过,一定查不到

由于一级缓存里没有改 bd 的信息,所以返回 null。

3. 在 else 中又获取了一次合并后的 bd(mbd),确定 mbd 可以创建实例,即 mbd 为非抽象的。

因为抽象的 bd 只能被继承使用,不能去实例化。

4. 优先加载当前 mbd 依赖的 DependsOn BeanDefinition

DependsOn 用于声明当前 bean 依赖于另外一个bean,所依赖的 bean 会被容器确保在当前 bean 实例化之前被实例化。

单例模式-构造参数依赖

A5. 尝试去一级缓存里拿,一定拿不到

A6. 记录当前 beanName 到 CreationSet 中

getSingleton#beforeSingletonCreation

image.png

A7. 开始创建实例对象


createBean
    |--> doCreateBean
            |--> createBeanInstance

通过 class 拿到 Constructor 构造器,进行反射创建对象。

A8. 发现构造器有一个参数类型为 B

但是构造方法可以是有参数,也有可能是无参数,我们先说有参数。就像开篇描述循环依赖一样 A 中有 B,那么我们在执行 createBeanInstance 方法时就要先去实例化 B。

A9. 需要先实例化 B

然后我们有重新执行了上面的步骤,只不过这次是执行的 B 类型。

B.7、B.8、B.9

通过 class 拿到 Constructor 构造器,进行反射创建对象
    | ——>发现构造器有一个参数类型 A
            |——> 需要先实例化 A

然后又回去了[尴尬],回到上面 4.优先加载 mbd。 A5. 尝试去一级缓存里拿,一定拿不到(1~4 不变,从第五步说) A6. 检测出循环依赖 终于发生变化了。记录当前 BeanName 到 CreateSet 中.. 但是发现 add 失败了。原来之前 set 中已经插入过 A 了,检测出循环依赖。

抛出循环依赖异常 那么单例 bean 的循环依赖 - 构造参数依赖 就检测出来了。

原型的循环依赖检测 接下来是原型的循环依赖检测,我们从 AbstractBeanFactory.doGetBean 开始。

在循环依赖检测的前 4 步是不变的,我们直接从第五步开始。

A5` 向 threadLocal 里添加当前 beanName

在上面代码中我们可以看到,进入 else if 里的时候,里面有一个 beforePrototypeCreation 的方法,该方法就是先去 prototypesCurrentlyInCreation(一个 threadLocal)中去获取,如果没有则添加。

A6` 通过 Class 拿到 Constructor 构造器,将要进行反射创建对象

A7` 发现构造器有一个参数类型 B

A8` 需要先实例化 B

然后经历了像之前单例构造器循环依赖一样的步骤,实例化 B 的过程又发现有 A,去创建 A。在创建 A 的过程中又会调用 doGetBean(),然后会走如下代码:

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

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null &&
            (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

在进入 isPrototypeCurrentlyInCreation 方法中,会对 prototypesCurrentlyInCreation 里是否含有 A 进行判断,如果有则返回 true。那么返回 true 后会抛循环依赖异常!

循环依赖解决过程

我们知道,单例 bean 的循环依赖(setter 产生依赖)是可以解决的。那我们来简单分析一下: 前四步和之前一样,这里我直接从第五步开始。

5. 去一级缓存里拿,一定拿不到。

doGetBean
    |--> getSingleton
            |--> beforeSingletonCreation  //这个方法我们很熟悉了吧。

6. 记录当前 beanName 到 prototypesCurrentlyInCreation 中

通过 beforeSingletonCreation,我们将 beanName 放入 threadLocal中。

7. 通过 class 拿到无参 Cunstructor 构造器,进行反射创建对象

8. 反射创建完毕,此时得到一个早期对象

什么是早期对象?(半成品)
① 未进行属性注入(无参构造)
② 未进行对象 init 方法调用
③ 未进行后处理器处理
但早期对象和处理完成后的对象内存地址是一样的,都是同一个对象。

9. 执行 MegerBeanDefinitionPostProcessor 操作,如处理 @Autowire 等

image.png 小知识,@Autowire 是 setter 注入。

10. 将早期对象封装成 ObjectFactory,放到三级缓存中

image.png

11. 进行依赖注入 populateBean

try {
    //属性填充,自动注入 ---通用方法
    populateBean(beanName, mbd, instanceWrapper);
    exposedObject = initializeBean(beanName, exposedObject, mbd);
}

12. 这时发现依赖了 B 类型,进行 getBean(B) 操作

接下来的操作和 实例化A 是一样的,B 也将自己的早期实例对象放到三级缓存中。然后属性注入 populateBean,发现 B 又依赖了 A,进行 getBean(A)操作。

getBean
    |--> doGetBean
            |--> getSingleton
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    // 去一级缓存中拿
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 去二级缓存中拿
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 去三级缓存中拿
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

然后又获取合并后的 bd 信息。 接下来就像 getSingleton 一样 ,先去一级缓存中拿,拿不到;检查 CurrentlyInCreation,发现 A 正在创建;去三级缓存中发现了 A 的早期实例;最后将 A 升级到二级缓存并清空三级缓存后返回 A 对象。 那么 B 的依赖注入完成了,最终将对象 B 放至一级缓存,并且清除掉二三级有关 B 的信息

13. 完成依赖注入

接上面第 12 步,注入 B 完成。

14. 最终

A 的依赖注入完成了,最终将对象 A 放至一级缓存,并且清除掉二三级有关 A 的信息。

至此,单例无参依赖注入解决完毕。

结束

依赖注入从检测到解决的所有过程就全部结束了,面试按照这个说绝对没问题。由于没有图,可能只是文字描述会很生涩,我会在后面继续对循环依赖做补充,顺便解决很多简写的东西。毕竟如果全部展开太多了。这也是我第一篇 Spring 文章写循环依赖的原因,因为它能牵扯出太多东西。 最后感谢观看,如果有问题请和我一起探讨一起进步。

感谢 B 站【小刘讲源码】 space.bilibili.com/457326371/