定义、举例
循环依赖是指Bean对象循环引用,两个或多个bean之间相互持有对方的引用,导致Spring容器无法初始化它们。
例如,A引用了B,发现B还没创建,于是开始创建B,在创建B的过程中发现B又引用了A,但A还没创建好。
出现循环依赖的场景
- Bean对象实例化阶段(因为在实例化时需要解析依赖关系)
- JVM使用引用计数器进行垃圾回收时,可能会出现循环依赖导致内存泄漏
- 线程互相占用对方的锁,发生死锁
通过三级缓存解决循环依赖
- 一级缓存(Singleton Objects):单例池、存放完全初始化后的 Bean。
- 二级缓存(Early Singleton Objects):缓存实例化但未初始化的 Bean(半成品)。
- 三级缓存(Singleton Factories):缓存的是ObjectFactory,表示对象工厂,用于生成普通对象或者代理对象,暴露 Bean到二级缓存。
只用两级缓存可以吗?
如果只用一级缓存和二级缓存,Spring 可以处理 普通的循环依赖(例如两个 Bean 互相依赖),但无法处理 涉及代理对象(AOP 动态代理)的循环依赖。
构造方法出现了循环依赖怎么解决?
注入方式是构造函数的情况下:
原因:由于 bean 的生命周期中构造函数是第一个执行的, spring 框架并不能解决构造函数的的
依赖注入
解决方案:使用 @Lazy 进行懒加载,什么时候需要对象再进行 bean 对象的创建。
示例:
@Component
public class BeanA {
private final BeanB beanB;
@Autowired
public BeanA(@Lazy BeanB beanB) { // 使用 @Lazy 延迟加载 BeanB
this.beanB = beanB;
}
}
@Component
public class BeanB {
private final BeanA beanA;
@Autowired
public BeanB(@Lazy BeanA beanA) { // 使用 @Lazy 延迟加载 BeanA
this.beanA = beanA;
}
}
- 当Spring创建BeanA时,发现它依赖BeanB,但由于@Lazy的存在,Spring不会立即初始化BeanB,而是注入一个代理对象。
- 当BeanA真正使用到BeanB时,代理对象会出发BeanB的初始化。
- 同时,BeanB依赖BeanA也会延迟加载。
@Lazy的优缺点:
优点:
- 解决构造函数注入的循环依赖问题。
- 延迟加载可以减少启动时的初始化开销。
缺点:
- 增加了代理对象的复杂性,可能影响调试。
- 如果滥用
@Lazy
,可能导致程序行为难以预测。