循环依赖发生在两个或两个以上的bean互相持有对方,形成闭环。Spring框架允许循环依赖存在,并通过三级缓存解决大部分循环依赖问题:
-
一级缓存:单例池,缓存已完成初始化的bean对象。
-
二级缓存:缓存尚未完成生命周期的早期bean对象。
-
三级缓存:缓存
ObjectFactory
,用于创建bean对象。
解决循环依赖的流程如下:
-
实例化A对象,并创建
ObjectFactory
存入三级缓存。 -
A在初始化时需要B对象,开始B的创建逻辑。
-
B实例化完成,也创建
ObjectFactory
存入三级缓存。 -
B需要注入A,通过三级缓存获取
ObjectFactory
生成A对象,存入二级缓存。 -
B通过二级缓存获得A对象后,B创建成功,存入一级缓存。
-
A对象初始化时,由于B已创建完成,可以直接注入B,A创建成功存入一级缓存。
-
清除二级缓存中的临时对象A。
-
构造方法出现了循环依赖怎么解决?
由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@Lazy
懒加载注解,延迟bean的创建直到实际需要时。
1. 循环依赖的常见场景
- 场景示例:
java
@Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; }
A
依赖B
,B
又依赖A
,形成循环依赖。
2. Spring解决循环依赖的机制
**(1) 三级缓存**
Spring容器通过三个缓存区管理Bean的创建过程:
- 一级缓存(singletonObjects):存放完全初始化后的单例Bean。
- 二级缓存(earlySingletonObjects):存放早期暴露的单例Bean(已实例化但未完成属性注入)。
- 三级缓存(singletonFactories):存放Bean工厂对象,用于生成早期Bean引用。
**(2) 解决流程**
以A
和B
循环依赖为例:
-
创建Bean A:
- 实例化A对象(调用构造函数)。
- 将A的工厂对象(用于生成早期引用)放入三级缓存。
- 开始注入A的属性,发现需要Bean B。
-
创建Bean B:
- 实例化B对象。
- 将B的工厂对象放入三级缓存。
- 开始注入B的属性,发现需要Bean A。
-
获取Bean A的早期引用:
- 从三级缓存中获取A的工厂对象,生成A的早期引用。
- 将A的早期引用放入二级缓存,并移除三级缓存中的A工厂。
-
完成Bean B的初始化:
- 将A的早期引用注入到B中。
- B完成初始化后,放入一级缓存。
-
完成Bean A的初始化:
- 从一级缓存获取B的实例,注入到A中。
- A完成初始化后,放入一级缓存,移除二级缓存。
3. 解决循环依赖的前提条件
- 必须满足以下条件:
- Bean必须是单例(Singleton):原型(Prototype)作用域的Bean无法解决循环依赖。
- 依赖注入方式必须为Setter或字段注入:构造器注入无法解决循环依赖(因实例化和注入同时进行)。
4. 构造器注入导致的循环依赖
若使用构造器注入,Spring无法提前暴露Bean的引用,导致循环依赖无法解决。
示例:
java
@Service
public class A {
private final B b;
public A(B b) { this.b = b; } // 构造器注入
}
@Service
public class B {
private final A a;
public B(A a) { this.a = a; } // 构造器注入
}
解决方案:
- 改用Setter或字段注入。
- 使用
@Lazy
延迟加载其中一个Bean:java
@Service public class A { private final B b; public A(@Lazy B b) { this.b = b; } // 延迟加载B }
5. 原型(Prototype)作用域的循环依赖
原型Bean每次请求都会创建新实例,Spring无法通过三级缓存解决其循环依赖,会抛出异常:
java
BeanCurrentlyInCreationException: Error creating bean with name 'a':
Requested bean is currently in creation: Is there an unresolvable circular reference?
解决方案:
- 避免在原型Bean中使用循环依赖。
- 使用
@Lazy
延迟加载其中一个Bean:java
@Scope("prototype") @Service public class A { @Autowired @Lazy private B b; }
6. 使用@Lazy注解延迟加载
@Lazy
注解可延迟Bean的初始化,直到首次使用时才创建实例,从而打破循环依赖。
示例:
java
@Service
public class A {
@Autowired
@Lazy
private B b;
}
7. 强制禁止循环依赖
在Spring Boot中可通过配置关闭循环依赖支持:
properties
spring.main.allow-circular-references=false
8. 最佳实践
- 优先使用Setter/字段注入:避免构造器注入导致无法解决的循环依赖。
- 检查设计合理性:循环依赖可能意味着代码耦合度高,建议通过重构解耦。
- 利用IDE工具分析依赖:如IntelliJ IDEA的"Analyze -> Analyze Module Dependencies"功能。
总结
Spring通过三级缓存机制和提前暴露未完全初始化的Bean引用,解决了单例Bean的循环依赖问题。开发者需注意:
- 避免在构造器注入中形成循环依赖。
- 原型Bean的循环依赖需通过
@Lazy
或重构解决。 - 合理设计代码结构,减少不必要的依赖。