首页>>后端>>Spring->深度解析Spring bean循环依赖及解决方案

深度解析Spring bean循环依赖及解决方案

时间:2023-11-29 本站 点击:0

Java 中的循环依赖

什么是循环依赖

例如,有下面两个类

classA{privateBb;publicvoidsetB(Bb){this.b=b;}}classB{privateAa;publicvoidsetA(Aa){this.a=a;}}

A 对象需要 B 对象,B 对象中需要 A 对象,相互需要,所以被称为循环依赖

怎么创建循环依赖的对象

要创建上面这种循环依赖的对象,可以这样做

publicstaticvoidmain(String[]args){Aa=newA();Bb=newB();//依赖注入a.setB(b);b.setA(a);}

核心思想就是,先实例化,后设置依赖

如上图,是一次运行的结果,为了更好的方便大家理解这种循环依赖对象的创建过程,我画了一张图

执行完第一句代码后:堆中有了 A 类的实例 A@491,其中属性 b 为 null

执行完第二句代码后:堆中有了 B 类的实例 B@492,其中属性 a 为 null

执行完第三句代码后:堆中 A@491 的属性 b 指向 B@492

执行完第四句代码后:堆中 B@492 的属性 a 指向 A@491

代码中的 A 中有 B,B 中有 A ,体现在堆上,其实 A@491 中有一个指向 B@492 的引用,B@492 中也有一个指向 A@491 的引用

这样我们就创建出了循环依赖的对象

什么样的循环依赖无法解决

如果某种循环依赖的对象无法先实例化,后设置依赖,那么这种循环依赖是无解的!

例如,A、B 通过构造方法设置依赖

classA{privateBb;publicA(Bb){this.b=b;}}classB{privateAa;publicB(Aa){this.a=a;}}

这种循环依赖,在实例化时,就需要依赖对象,而依赖对象在实例化时又需要自己,因此这种循环依赖是无解的!

也就是说并不是所有的循环依赖都可以解决!

Spring 循环依赖

Spring 解决循环依赖的思路跟上面的一样,先实例化,后设置依赖。同样,对于通过构造方法造成的循环依赖无法解决!

Spring 能解决哪种场景下的循环依赖

条件一:依赖注入方式!

在 Spring 中,依赖注入有三种方式

关于 Spring 依赖注入的详情,参考 Spring 依赖注入

基于构造方法的依赖注入

基于setter()方法的依赖注入

基于成员变量的依赖注入

第一种基于构造方法的依赖注入,上面也提到了,如果产生了循环依赖问题,是无法解决的,其余两种如果产生了循环依赖问题,都可以通过先实例化,后设置依赖的方式解决

条件二:Bean 的 Scope

关于 Bean 的 Scope 的详情,参考 Spring Bean Scope

在 Spring 中, Bean 的 Scope 会直接影响 Bean 的创建行为,而创建行为则决定了是否能解决循环依赖问题,在Bean 的5种 Scope 中,只有 singleton Scope 的创建行为,在发生循环依赖问题时,Spring 框架可以自动帮你解决,前提是满足条件一

总结

发生循环依赖时,Spring 框架可以自动帮我们解决,但是有两个前提条件

Bean 基于成员变量或setter()方法的方式注入依赖

Bean 的 Scope 必须为 singleton

Spring singleton Bean 的生命周期

既然只有 singleton 作用域下,创建 Bean 时,才能解决 bean 的循环依赖,那我们就必须了解一下,创建 bean 的过程,这一块内容比较多,我单独写了一篇文章,参考 Spring singleton Bean 的生命周期

Spring bean 循环依赖案例分析

我们 A、B 两个类,他们相互依赖

@ServiceclassB{privateAa;@AutowiredpublicvoidsetA(Aa){this.a=a;}}@ServiceclassA{privateBb;@ResourcepublicvoidsetB(Bb){this.b=b;}}

要解决循环依赖必须使用基于成员变量或setter()方法的方式注入依赖,Spring 不推荐使用基于成员变量的方式注入依赖,所以我使用的是 setter()方法的方式,注意,这里特意用了 @Autowired@Resource 两种注解,两者都可以进行自动装配,效果一样

现在我们结合 Spring singleton Bean 的生命周期 中的流程,来梳理 Spring 是如何解决这种循环依赖

假设 spring 是先创建 A, singleton 作用域下,创建 Bean 时,会调用 doGetBean() ,先从缓存中获取,获取不到在创建

调用doGetBean(nameA,...) 方法,获取/创建 beanA

先调用 getSingleton(nameA) 从三级缓存中查找,如果找到直接返回,此时三级缓存还是空的,因此找不到

没找到就调用 getSingleton(nameA, ()->{createBean(nameA,...)}) 去创建

getSingleton() 方法内部会调用 createBean(nameA,...) 创建 beanA

createBean(nameA,...) 方法内部会首先调用 createBeanInstance(nameA,...)来实例化 beanA,此时虽然有 beanA 对象了,但是 beanA 内部的属性 b 还是 null ,需要等待后期填充

调用 addSingletonFactory(nameA, () -> getEarlyBeanReference(nameA, mbd, beanA)) 方法将能够获取到 beanA 或 beanA的代理对象的匿名函数(bean 工厂)放入第三级缓存中

调用populateBean(nameA, mbd, beanA)方法填充 beanA 的属性a,在该方法里面最终会调用 doGetBean(nameB,...) 方法,获取/创建 beanB

调用doGetBean(nameA,...) 方法,获取/创建 beanA

先调用 getSingleton(nameA) 从三级缓存中查找,此时第三级缓存中有 beanA 的工厂,因此会将 beanA 的工厂从第三级缓存拉出来,调用获取方法,获取 beanA的"提前引用",这个例子中由于没有对 A 使用 AOP 因此,这里拿到的就是原始的 beanA,否则,拿到的将是 beanA 的代理

将第三级缓存中 beanA 的工厂删掉,然后将 beanA的"提前引用"放入二级缓存中

返回 beanA的"提前引用"

调用doGetBean(nameB,...) 方法,获取/创建 beanB

先调用 getSingleton(nameB) 从三级缓存中查找,如果找到直接返回,此时只有第三级缓存中有一个 beanA 的工厂方法,因此找不到

没找到就调用 getSingleton(nameB, ()->{createBean(nameB,...)}) 去创建

getSingleton() 方法内部会调用 createBean(nameB,...) 创建 beanB

createBean(nameB,...) 方法内部会首先调用 createBeanInstance(nameB,...)来实例化 beanB,此时虽然有 beanB 对象了,但是 beanB 内部的属性 a 还是 null ,需要等待后期填充

调用 addSingletonFactory(nameB, () -> getEarlyBeanReference(nameB, mbd, beanB)) 方法将能够获取到 beanB 或 beanB的代理对象的匿名函数(bean 工厂)放入第三级缓存中

调用populateBean(nameB, mbd, beanB)方法填充 beanB 的属性a,在该方法里面最终会调用 doGetBean(nameB,...) 方法,获取 beanA

此时 beanB 的属性已经填充好了,属性a是 beanA的"提前引用"。然后调用 initializeBean(nameB, beanB, mbd) 进行初始化

将创建完成的 beanB 放入到第一级缓存中,然后删除掉第二、三级缓存中 beanB 的内容

返回beanB

此时 beanA 的属性已经填充好了,属性b是第一级缓存中的 beanB。然后调用 initializeBean(nameA, beanA, mbd) 进行初始化

将创建完成的 beanA 放入到第一级缓存中,然后删除掉第二、三级缓存中 beanA 的内容

返回beanA

以上是 A 、B 没有被 AOP 代理的情况,如果被代理了,其实也差不多,唯一的区别在于从第三级缓存中拿到 bean 工厂后调用获取方法拿到的不是原始 bean 对象,而是 bean 的代理对象,其他地方都一样。

作者:我妻礼弥著作权归作者所有。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Spring/281.html