一、循环依赖产生的原因

我们知道Spring最具盛名的就是依赖注入,而循环依赖就是指多个bean相互依赖,形成了一个闭环,比如:A依赖于B、B依赖于C、C依赖于A。

简单看代码

class A {
    B b;
}

class B{
    C c;
}

class C{
    A a;
}

这就是最简单会产生循环依赖的代码

二、解决

在Spring内部中解决循环依赖问题,指的就是默认单利的Bean中,属性相互引用的场景

也就是说:

1、默认的单例(singleton)的场景是支持循环依赖的,运行程序时不会报错。

2、原型(prototype)的场景是不支持循环依赖的,会报错

我们接下来解释上面这两条

三级缓存

在说明原因之前,我们首先介绍三级缓存

Spring为了提高效率,在内部创建了三级缓存

第一级缓存(也叫单例池) singletonObjects :存放已经经历了完整生命周期的 Bean 对象

第二级缓存: earlySingletonObjects ,存放早期暴露出来的 Bean 对象, Bean 的生命周期未结束(属性还未填充完成)

第三级缓存: Map < String , ObjectFactory <?>> singletonFactories ,存放可以生成 Bean 的工厂

注意:

只有单例的 bean 会通过三级缓存提前暴露来解决循环依赖的问题, 而非单例的 bean每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的 bean 是没有缓存的,不会将其放到三级缓存中。

来解释一下:

如果我们使用的是单例模式,也就是说每次从Spring容器中取得的对象时同一个对象,那么这个对象的创建和保存就会应用到三级缓存。当我们使用完该单例对象时,Spring会将其存入一级缓存,也就是说当下一次获取对象时,我们Spring容器不会再重新创建对象,而是从一级缓存中直接获得。二级缓存存储的时生命周期还没有结束的对象,我们可以简单理解为已经实例化(建好了),但是还没有初始化的Bean(还没用)。三级缓存则存放的是创建对象所需的工厂(BeanFactory等)或正在创建的Bean。

如果我们采取的是原型模式,也就是每次获的该Bean对象都是一个新创建的对象,则之前用完的对象不会被放到三级缓存之中。

创建流程:

假设此时我们只有两个Bean的相互依赖

1、A创建过程中需要 B 于是 A 将自己放到三级缓里面,去实例化 B 

2、B实例化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A 

3、B顺利初始化完毕,将自己放到一级缓存里面(此时 B 里面的 A 依然是创建中状态)然后回来接着创建 A ,此时 B 己经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将 A 自己放到一级缓存里面。

注意:

此时如果选择使用构造函数注入,则可能会创建无法解决的循环依赖方案

原因引用某个大佬讲的:

通过构造方法之所以失败的主要原因就是,依赖的属性被提前实例化了,因为testA未完成实例化,内存中也就不存在有关testA的任何记录,自然缓存中也没有,所以当再次触发testA的实例化时,也没法从缓存中获取,最终通过一个set集合(可以理解为判断哪些Bean开始实例化的一个集合)判断是否被重复添加来控制,避免陷入死循环