Spring bean 循环依赖
Java 中的循环依赖
什么是循环依赖
例如,有下面两个类
class A {
private B b;
public void setB(B b) {this.b = b;}
}
class B {
private A a;
public void setA(A a) {this.a = a;}
}
A 对象需要 B 对象,B 对象中需要 A 对象,相互需要,所以被称为循环依赖
怎么创建循环依赖的对象
要创建上面这种循环依赖的对象,可以这样做
public static void main(String[] args) {
A a = new A();
B b = new B();
// 依赖注入
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 通过构造方法设置依赖
class A {
private B b;
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public B(A a) {
this.a = a;
}
}
这种循环依赖,在实例化时,就需要依赖对象,而依赖对象在实例化时又需要自己,因此这种循环依赖是无解的!
也就是说并不是所有的循环依赖都可以解决!
Spring 循环依赖
Spring 解决循环依赖的思路跟上面的一样,先实例化,后设置依赖。同样,对于通过构造方法造成的循环依赖无法解决!
Spring 能解决哪种场景下的循环依赖
条件一:依赖注入方式!
在 Spring 中,依赖注入有三种方式
- 基于构造方法的依赖注入
- 基于
setter()
方法的依赖注入 - 基于 成员变量 的依赖注入
第一种基于构造方法的依赖注入,上面也提到了,如果产生了循环依赖问题,是无法解决的(真的吗?参考后记),其余两种如果产生了循环依赖问题,都可以通过先实例化,后设置依赖的方式解决
条件二:Bean 的 Scope
在 Spring 中, Bean 的 Scope 会直接影响 Bean 的创建行为,而创建行为则决定了是否能解决循环依赖问题,在Bean 的5种 Scope 中,只有 singleton Scope 的创建行为,在发生循环依赖问题时,Spring 框架可以自动帮你解决,前提是满足条件一
总结
发生循环依赖时,Spring 框架可以自动帮我们解决,但是有两个前提条件
- Bean 基于成员变量或
setter()
方法的方式注入依赖 - Bean 的 Scope 必须为 singleton
Spring singleton Bean 的生命周期
Spring bean 循环依赖案例分析
我们 A、B 两个类,他们相互依赖
@Service
class B {
private A a;
@Autowired
public void setA(A a) {this.a = a;}
}
@Service
class A {
private B b;
@Resource
public void setB(B b) {this.b = b;}
}
要解决循环依赖必须使用基于成员变量或 setter()
方法的方式注入依赖,Spring 不推荐使用基于成员变量的方式注入依赖,所以我使用的是 setter()
方法的方式,注意,这里特意用了 @Autowired
、@Resource
两种注解,两者都可以进行自动装配,效果一样
假设 spring 是先创建 A, singleton 作用域下,创建 Bean 时,会调用 doGetBean()
,先从缓存中获取,获取不到在创建
- 调用
doGetBean(nameA,...)
方法,获取/创建 beanA - 先调用
getSingleton(nameA)
从三级缓存中查找,如果找到直接返回,此时三级缓存还是空的,因此找不到 - 没找到就调用
getSingleton(nameA, ()->{createBean(nameA,...)})
去创建 getSingleton()
方法内部会调用createBean(nameA,...)
创建 beanAcreateBean(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(nameB,...)
方法,获取/创建 beanB - 先调用
getSingleton(nameB)
从三级缓存中查找,如果找到直接返回,此时只有第三级缓存中有一个 beanA 的工厂方法,因此找不到 - 没找到就调用
getSingleton(nameB, ()->{createBean(nameB,...)})
去创建 getSingleton()
方法内部会调用createBean(nameB,...)
创建 beanBcreateBean(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- 调用
doGetBean(nameA,...)
方法,获取/创建 beanA - 先调用
getSingleton(nameA)
从三级缓存中查找,此时第三级缓存中有 beanA 的工厂,因此会将 beanA 的工厂从第三级缓存拉出来,调用获取方法,获取 beanA的"提前引用",这个例子中由于没有对 A 使用 AOP 因此,这里拿到的就是原始的 beanA,否则,拿到的将是 beanA 的代理 - 将第三级缓存中 beanA 的工厂删掉,然后将 beanA的"提前引用"放入二级缓存中
- 返回 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 的代理对象,其他地方都一样
后记
上面提到,基于构造方法的依赖注入方式,如果发生了循环依赖,无法解决,例如
class A {
private B b;
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public B(A a) {
this.a = a;
}
}
A 在实例化阶段,就需要依赖对象B,而依赖对象B在实例化阶段又要依赖A,因此我刚开始认为这种循环依赖是无法解决的
但评论区中一位读者提醒说,在 spring 中 基于构造方法的依赖注入方式,如果发生了循环依赖,可以通过加 @Lazy
注解解决
经过实践发现确实可行,那 spring 是如何解决的呢?
答案是动态代理,实例化 A 的时候,传入的不是 B,而是 B 的代理,它继承了 B,例如
class A {
private B b;
@Lazy
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public B(A a) {
this.a = a;
}
}
创建完成之后,对应的关系如下
B 的 CGLib代理对象继承了 B,可以替代 B ,行使 B 的所有能力,他们在内存中的结构如下
以上例子使用的是 singleton 作用域
转载自:https://juejin.cn/post/7002874888448917534