Spring 如何解决循环依赖 笔记251008
一句话总结:
Spring 只解决「单例 Bean 的字段 / setter 循环依赖」,靠的是三级缓存 + 提前暴露"半成品"引用;构造器注入、原型作用域的循环依赖直接抛异常。
下面把完整机制拆开讲清:
- 能解 vs 不能解的场景
依赖方式 | 作用域 | Spring 能否解决 | 原因 |
---|---|---|---|
字段 / setter 注入 | singleton | ✅ | 三级缓存可提前暴露对象引用 |
构造器注入 | singleton | ❌ | 实例化阶段就卡住,无法提前暴露 |
任何方式 | prototype | ❌ | 原型 Bean 不缓存,每次新建,无法复用引用 |
- 三级缓存到底做了什么
位置:DefaultSingletonBeanRegistry
缓存 | 变量 | 内容 | 生命周期阶段 |
---|---|---|---|
一级 singletonObjects |
ConcurrentHashMap |
完全初始化好的单例 | 就绪可用 |
二级 earlySingletonObjects |
ConcurrentHashMap |
提前曝光的"裸"对象(可能代理) | 已实例化,未填充属性 |
三级 singletonFactories |
HashMap<String, ObjectFactory<?>> |
能生成早期引用的 ObjectFactory |
实例化后马上放进去 |
- 字段注入循环依赖的完整时间线
以「A → B,B → A」字段注入为例:
-
getBean(A)
- 实例化 A(构造器完成)→ 创建
ObjectFactory
并放入三级缓存 - 开始填充 A 的字段,发现需要 B →
getBean(B)
- 实例化 A(构造器完成)→ 创建
-
getBean(B)
- 实例化 B → 也把自己的
ObjectFactory
放入三级缓存 - 填充 B 的字段,发现需要 A → 又去
getBean(A)
- 实例化 B → 也把自己的
-
此时 A 还在三级缓存,调用
ObjectFactory.getObject()
拿到早期引用(若需要 AOP 则在这里生成代理),把结果放进二级缓存 并删除三级缓存条目;B 拿到这个引用完成属性填充,初始化结束 → 放入一级缓存 -
B 返回后,A 继续完成属性填充 → 初始化结束 → 放入一级缓存
整个过程没有两次初始化,也不会出现不完整对象被外部拿到
- 构造器注入为什么不行
构造器注入时,实例化阶段就必须把依赖传进去;
此时 Spring 还没机会把"半成品"放进任何缓存,
于是出现「A 等 B,B 等 A」的死锁,直接抛:
BeanCurrentlyInCreationException
- 原型 Bean 为什么不行
原型 Bean 每次 getBean
都新建对象,不缓存;
无法复用同一个引用,因此无法通过"提前暴露"打破循环。
- 一句话记忆
单例 + 字段 / setter + 三级缓存 = 可解;
构造器 or 原型 = 无解。
- 开发建议
- 尽量用「字段 / setter 注入」或「@Autowired + @Lazy」延迟加载,避免构造器循环依赖
- 设计层面消除循环:抽离公共接口、事件驱动、第三方持有双方引用等
Spring通过三级缓存机制来解决单例Bean的循环依赖问题。下面详细讲解其工作原理。
核心原理:三级缓存
Spring在DefaultSingletonBeanRegistry
类中维护了三个缓存级别:
java
// 第一级缓存:存放完全初始化好的Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 第二级缓存:存放早期的Bean引用(半成品)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 第三级缓存:存放Bean工厂,用于创建早期引用
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
解决循环依赖的完整流程
以A依赖B,B依赖A为例:
步骤1:创建Bean A
java
// 1. 开始创建A,标记A为"创建中"
beforeSingletonCreation("A");
// 2. 实例化A(调用构造函数),此时A对象已存在但属性为null
instance = createBeanInstance("A");
// 3. 将A的ObjectFactory放入三级缓存
addSingletonFactory("A", () -> getEarlyBeanReference("A", mbd, instance));
此时三级缓存中有了A的工厂,可以生产A的早期引用。
步骤2:填充A的属性,发现依赖B
java
// 4. 填充A的属性
populateBean("A", mbd, instanceWrapper);
// 发现需要注入B,开始获取Bean B
步骤3:创建Bean B
java
// 5. 开始创建B
beforeSingletonCreation("B");
// 6. 实例化B
instance = createBeanInstance("B");
// 7. 将B的ObjectFactory放入三级缓存
addSingletonFactory("B", () -> getEarlyBeanReference("B", mbd, instance));
步骤4:填充B的属性,发现依赖A(循环依赖发生!)
java
// 8. 填充B的属性,发现需要注入A
// 开始获取Bean A的流程:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 第一级缓存查找(成品)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 第二级缓存查找(半成品)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 第三级缓存查找(工厂)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用工厂获取早期引用
singletonObject = singletonFactory.getObject();
// 将A从三级缓存升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
步骤5:完成B的创建
java
// 9. 将A的早期引用注入到B中
// 10. 完成B的初始化
initializeBean("B", exposedObject, mbd);
// 11. 将B放入一级缓存
addSingleton("B", singletonObject);
步骤6:完成A的创建
java
// 12. 回到A的创建流程,现在可以获取到完整的B
// 13. 将B注入到A中
// 14. 完成A的初始化
initializeBean("A", exposedObject, mbd);
// 15. 将A放入一级缓存
addSingleton("A", singletonObject);
关键方法解析
getEarlyBeanReference方法
这个方法负责处理AOP代理:
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
// 如果需要代理,这里会返回代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
addSingleton方法
java
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 放入一级缓存
this.singletonObjects.put(beanName, singletonObject);
// 从二、三级缓存移除
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
流程图解
三级缓存的分工
缓存级别 | 名称 | 内容 | 作用 |
---|---|---|---|
一级缓存 | singletonObjects | 完整的Bean | 提供最终可用的Bean |
二级缓存 | earlySingletonObjects | 早期Bean引用 | 避免重复创建早期引用 |
三级缓存 | singletonFactories | Bean工厂 | 处理AOP代理,按需创建代理对象 |
适用场景与限制
支持的循环依赖
- Setter注入 ✓
- Field注入 (@Autowired) ✓
- 单例Bean ✓
不支持的循环依赖
- 构造器注入 ✗
java
@Component
public class A {
private B b;
@Autowired
public A(B b) { // 构造器循环依赖无法解决
this.b = b;
}
}
- 原型Bean (prototype) ✗
- @Async注解的Bean ✗
总结
Spring通过三级缓存机制优雅地解决了循环依赖问题:
- 提前暴露:在Bean实例化后立即暴露引用
- 工厂模式:通过ObjectFactory延迟处理AOP代理
- 缓存升级:从三级→二级→一级的渐进式完善
- 代理兼容:确保代理对象的一致性
这种设计既解决了循环依赖,又保持了Spring AOP的正常工作,是Spring框架中非常精妙的设计之一。
1️⃣ 参考 1
Spring 如何解决循环依赖
1. 循环依赖的类型
1.1 常见的循环依赖场景
java
// 场景1:相互依赖
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
// 场景2:三组件循环依赖
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceC serviceC;
}
@Component
public class ServiceC {
@Autowired
private ServiceA serviceA;
}
// 场景3:自我依赖(少见但可能)
@Component
public class SelfDependent {
@Autowired
private SelfDependent self;
}
2. Spring 的解决方案
2.1 三级缓存机制
Spring 通过三级缓存 + 提前暴露引用的方式解决循环依赖:
java
// Spring 内部的三级缓存结构
public class DefaultSingletonBeanRegistry {
// 第一级缓存:完整Bean(已初始化完成)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 第二级缓存:早期Bean(已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 第三级缓存:Bean工厂(用于创建早期引用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 正在创建中的Bean
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}
2.2 详细解决流程
java
// 以 A → B → A 为例的详细流程
@Component
public class A {
@Autowired
private B b;
private String name = "A";
public void init() {
System.out.println("A initialized");
}
}
@Component
public class B {
@Autowired
private A a;
private String name = "B";
public void init() {
System.out.println("B initialized");
}
}
解决步骤:
java
// 步骤1:开始创建A
1. 调用 getBean("A")
2. 标记 A 正在创建:singletonsCurrentlyInCreation.add("A")
3. 实例化 A 对象(此时A是原始对象,b=null,未调用init方法)
// 步骤2:提前暴露A的引用
4. 将A的ObjectFactory放入三级缓存:
singletonFactories.put("A", () -> getEarlyBeanReference("A", mbd, aInstance))
// 步骤3:填充A的属性(发现依赖B)
5. 调用 populateBean("A"),发现需要注入B
6. 调用 getBean("B")
// 步骤4:开始创建B
7. 标记 B 正在创建:singletonsCurrentlyInCreation.add("B")
8. 实例化 B 对象
9. 将B的ObjectFactory放入三级缓存
// 步骤5:填充B的属性(发现依赖A)
10. 调用 populateBean("B"),发现需要注入A
11. 调用 getBean("A")
// 步骤6:获取A的早期引用
12. 发现A正在创建中
13. 从三级缓存获取A的ObjectFactory
14. 调用 factory.getObject() 获取A的早期引用
15. 将A从三级缓存移到二级缓存:earlySingletonObjects.put("A", aEarlyReference)
16. 返回A的早期引用给B
// 步骤7:完成B的创建
17. B持有A的早期引用,完成属性注入
18. 初始化B(调用@PostConstruct、init方法等)
19. 将B放入一级缓存,清理二三级缓存
// 步骤8:完成A的创建
20. A获得完整的B实例,完成属性注入
21. 初始化A
22. 将A放入一级缓存,清理二三级缓存
3. 源码实现解析
3.1 核心方法:doCreateBean
java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化Bean
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. 提前暴露引用(解决循环依赖的关键)
if (earlySingletonExposure) {
// 将Bean工厂添加到三级缓存
addSingletonFactory(beanName,
() -> getEarlyBeanReference(beanName, mbd, bean));
}
// 3. 属性注入(可能触发循环依赖)
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
3.2 核心方法:getSingleton
java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存查找
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 从二级缓存查找
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 4. 创建早期引用
singletonObject = singletonFactory.getObject();
// 5. 移动到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3.3 核心方法:addSingletonFactory
java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 放入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 清理二级缓存(确保数据一致性)
this.earlySingletonObjects.remove(beanName);
}
}
}
4. AOP 代理的处理
4.1 代理对象的循环依赖
java
@Component
public class TransactionalService {
@Autowired
private AnotherService anotherService;
@Transactional
public void doSomething() {
// 事务方法
}
}
@Component
public class AnotherService {
@Autowired
private TransactionalService transactionalService; // 需要代理对象
}
Spring 的处理方式:
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
// AbstractAutoProxyCreator 在这里创建代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
5. 无法解决的循环依赖场景
5.1 构造器注入循环依赖
java
@Component
public class ConstructorA {
private final ConstructorB b;
@Autowired
public ConstructorA(ConstructorB b) { // 构造器注入
this.b = b;
}
}
@Component
public class ConstructorB {
private final ConstructorA a;
@Autowired
public ConstructorB(ConstructorA a) { // 构造器注入
this.a = a;
}
}
错误信息:
less
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| constructorA defined in file [...]
↑ ↓
| constructorB defined in file [...]
└─────┘
5.2 原型作用域的循环依赖
java
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeA {
@Autowired
private PrototypeB b;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeB {
@Autowired
private PrototypeA a;
}
原因:原型 Bean 不缓存,每次都会创建新实例。
5.3 @Async 方法的循环依赖
java
@Component
public class AsyncService {
@Autowired
private AnotherService anotherService;
@Async
public void asyncMethod() {
// 异步方法
}
}
@Component
public class AnotherService {
@Autowired
private AsyncService asyncService; // 需要代理对象
}
6. 解决方案和最佳实践
6.1 避免循环依赖的设计
java
// 方案1:使用接口分离
public interface IServiceA {
void methodA();
}
public interface IServiceB {
void methodB();
}
@Component
public class ServiceA implements IServiceA {
@Autowired
private IServiceB serviceB;
}
@Component
public class ServiceB implements IServiceB {
@Autowired
private IServiceA serviceA;
}
// 方案2:使用 setter 注入替代字段注入
@Component
public class SetterA {
private SetterB b;
@Autowired
public void setB(SetterB b) {
this.b = b;
}
}
// 方案3:使用 @Lazy 延迟加载
@Component
public class LazyA {
@Lazy
@Autowired
private LazyB b;
}
6.2 应用启动时检测
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
// 开启循环依赖检测(默认就是true)
app.setAllowCircularReferences(true);
app.run(args);
}
}
7. 调试和排查技巧
7.1 启用调试日志
properties
########## application.properties
logging.level.org.springframework.beans.factory=DEBUG
logging.level.org.springframework.context=DEBUG
########## 或者查看具体的循环依赖信息
spring.main.allow-circular-references=false
########## 强制检测
7.2 使用 BeanPostProcessor 调试
java
@Component
public class CircularDependencyDebugProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean initialized: " + beanName + " - " + bean.getClass().getSimpleName());
return bean;
}
}
8. 总结
Spring 通过三级缓存机制优雅地解决了循环依赖问题:
- ✅ 支持场景:单例 Bean 的字段注入、setter 注入
- ❌ 不支持场景:构造器注入、原型 Bean、某些代理场景
- 🔧 解决方案:提前暴露引用 + 缓存不同状态的 Bean
- ⚡ 性能考虑:空间换时间,保证单例 Bean 的正确性
最佳实践:
- 尽量避免循环依赖,优化设计
- 优先使用构造器注入(可以及早发现问题)
- 必要时使用 @Lazy 或 setter 注入
- 保持 Bean 的轻量,避免复杂的依赖关系
2️⃣ 参考 2
Spring 如何解决循环依赖
1. 循环依赖的类型
1.1 构造器循环依赖(无法解决)
java
@Service
public class AService {
private final BService bService;
public AService(BService bService) { // 构造器注入
this.bService = bService;
}
}
@Service
public class BService {
private final AService aService;
public BService(AService aService) { // 构造器注入
this.aService = aService;
}
}
结果 :Spring 会抛出 BeanCurrentlyInCreationException
1.2 Setter/Field 循环依赖(可以解决)
java
@Service
public class AService {
@Autowired
private BService bService; // 字段注入
}
@Service
public class BService {
@Autowired
private AService aService; // 字段注入
}
2. 三级缓存机制
Spring 通过三级缓存来解决 Setter/Field 循环依赖:
2.1 三级缓存结构
java
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整的 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期的 Bean(已实例化但未完成属性注入)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:ObjectFactory 缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
3. 详细解决流程
3.1 AService 和 BService 创建过程
java
// 步骤1:开始创建 AService
1. getBean("aService") → createBean("aService")
2. doCreateBean("aService"):
- 实例化 AService 对象(调用构造函数)
- 将 AService 包装成 ObjectFactory 放入三级缓存
- 代码:addSingletonFactory("aService", () -> getEarlyBeanReference(...))
// 此时缓存状态:
// 一级缓存:{}
// 二级缓存:{}
// 三级缓存:{"aService": ObjectFactory}
// 步骤2:填充 AService 属性
3. populateBean("aService") → 发现需要 bService
4. getBean("bService") → createBean("bService")
// 步骤3:开始创建 BService
5. doCreateBean("bService"):
- 实例化 BService 对象
- 将 BService 包装成 ObjectFactory 放入三级缓存
// 此时缓存状态:
// 一级缓存:{}
// 二级缓存:{}
// 三级缓存:{"aService": ObjectFactory, "bService": ObjectFactory}
// 步骤4:填充 BService 属性
6. populateBean("bService") → 发现需要 aService
7. getBean("aService") → getSingleton("aService")
// 关键步骤:获取 AService 的早期引用
8. getSingleton("aService") 执行过程:
- 一级缓存:未找到
- 二级缓存:未找到
- 三级缓存:找到 ObjectFactory
- 调用 objectFactory.getObject() 获取 AService 早期引用
- 将 AService 从三级缓存升级到二级缓存
// 此时缓存状态:
// 一级缓存:{}
// 二级缓存:{"aService": AService@早期引用}
// 三级缓存:{"bService": ObjectFactory}
// 步骤5:继续 BService 创建
9. 将 AService 早期引用注入到 BService
10. BService 完成初始化,放入一级缓存
// 此时缓存状态:
// 一级缓存:{"bService": BService@完整对象}
// 二级缓存:{"aService": AService@早期引用}
// 三级缓存:{}
// 步骤6:继续 AService 创建
11. 将完整的 BService 注入到 AService
12. AService 完成初始化,放入一级缓存
// 最终缓存状态:
// 一级缓存:{"aService": AService@完整对象, "bService": BService@完整对象}
// 二级缓存:{}
// 三级缓存:{}
3.2 核心源码解析
java
// 获取单例 Bean 的核心方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存查找完整 Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 从二级缓存查找早期 Bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 创建早期引用(可能返回代理对象)
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
// 添加 ObjectFactory 到三级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
}
}
}
4. AOP 代理的特殊处理
4.1 为什么需要三级缓存?
java
// ObjectFactory 的创建
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
// 如果 Bean 需要代理,这里会返回代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
关键点:
- 三级缓存中的 ObjectFactory 可以处理 AOP 代理
- 如果直接使用二级缓存,无法处理代理对象的循环依赖
- ObjectFactory 确保在整个过程中,同一个 Bean 名称返回的是同一个代理对象
5. 无法解决的循环依赖场景
5.1 构造器注入
java
@Component
public class CircularA {
private final CircularB circularB;
public CircularA(CircularB circularB) { // 构造器注入
this.circularB = circularB;
}
}
@Component
public class CircularB {
private final CircularA circularA;
public CircularB(CircularA circularA) { // 构造器注入
this.circularA = circularA;
}
}
原因:Bean 在实例化阶段就需要依赖对象,但此时 Bean 还未创建,无法放入三级缓存。
5.2 原型作用域的 Bean
java
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeA {
@Autowired
private PrototypeB prototypeB;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeB {
@Autowired
private PrototypeA prototypeA;
}
原因:原型 Bean 不会放入缓存,每次都会创建新实例。
5.3 @Async 方法的循环依赖
java
@Service
public class AsyncServiceA {
@Autowired
private AsyncServiceB serviceB;
@Async
public void asyncMethod() {
// 异步方法
}
}
@Service
public class AsyncServiceB {
@Autowired
private AsyncServiceA serviceA;
}
原因:@Async 会创建代理对象,可能在某些情况下导致循环依赖解决失败。
6. 解决方案和最佳实践
6.1 避免循环依赖的设计
java
// 好的设计:使用接口解耦
public interface UserService {
User findUser(Long id);
}
public interface OrderService {
Order findOrder(Long id);
}
@Service
public class UserServiceImpl implements UserService {
// 不直接依赖具体实现
@Autowired
private OrderService orderService;
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserService userService;
}
6.2 使用 @Lazy 注解
java
@Service
public class ServiceA {
@Lazy // 延迟注入
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
6.3 使用 setter 注入
java
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) { // setter 注入
this.serviceB = serviceB;
}
}
6.4 使用 ApplicationContextAware
java
@Service
public class ServiceA implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void doSomething() {
// 在需要的时候再获取 Bean
ServiceB serviceB = applicationContext.getBean(ServiceB.class);
}
}
7. 调试技巧
7.1 查看循环依赖信息
java
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) {
// 检查是否存在循环依赖
if (applicationContext instanceof ConfigurableApplicationContext) {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)
((ConfigurableApplicationContext) applicationContext).getBeanFactory();
// 查看正在创建的 Bean
Set<String> singletonsInCreation = beanFactory.getSingletonsCurrentlyInCreation();
System.out.println("正在创建的Bean: " + singletonsInCreation);
}
}
}
8. 总结
Spring 通过三级缓存机制优雅地解决了单例 Bean 的字段/Setter 注入循环依赖问题:
-
三级缓存分工:
- 一级缓存:存储完整的 Bean
- 二级缓存:存储早期的 Bean 引用
- 三级缓存:存储 ObjectFactory,支持 AOP 代理
-
解决时机:在 Bean 实例化后、属性注入前暴露引用
-
限制条件:
- 只适用于单例 Bean
- 只适用于字段注入和 Setter 注入
- 不适用于构造器注入
-
最佳实践:
- 尽量避免循环依赖
- 使用接口解耦
- 必要时使用 @Lazy 注解
理解 Spring 的循环依赖解决机制,有助于我们设计更健壮的应用程序架构。
3️⃣ 参考 3
Spring 如何解决循环依赖
什么是循环依赖
循环依赖是指两个或多个 Bean 相互依赖,形成闭环:
java
// 情况1:直接相互依赖
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
// 情况2:间接循环依赖
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private C c;
}
@Component
public class C {
@Autowired
private A a;
}
Spring 的解决方案
核心机制:三级缓存
Spring 通过三级缓存机制解决循环依赖问题:
java
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期Bean
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
}
解决流程详解
场景:A ↔ B 相互依赖
步骤1:开始创建 A
java
// 1. 调用 getBean("A")
// 2. 标记 A 正在创建中
beforeSingletonCreation("A");
// 3. 实例化 A(调用构造方法)
Object A = createBeanInstance("A");
// 4. 将 A 的 ObjectFactory 放入三级缓存
addSingletonFactory("A", () -> getEarlyBeanReference("A", mbd, A));
// 5. 开始属性注入,发现需要 B
populateBean("A");
步骤2:A 需要 B,开始创建 B
java
// 1. 调用 getBean("B")
// 2. 标记 B 正在创建中
beforeSingletonCreation("B");
// 3. 实例化 B(调用构造方法)
Object B = createBeanInstance("B");
// 4. 将 B 的 ObjectFactory 放入三级缓存
addSingletonFactory("B", () -> getEarlyBeanReference("B", mbd, B));
// 5. 开始属性注入,发现需要 A
populateBean("B");
步骤3:B 获取 A 的早期引用
java
// B 在 populateBean 时调用 getBean("A")
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 第一次查找:一级缓存(没有A,因为A还没创建完成)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 第二次查找:二级缓存(没有A)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 第三次查找:三级缓存(找到A的ObjectFactory)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过ObjectFactory获取A的早期引用
singletonObject = singletonFactory.getObject();
// 将A从三级缓存移动到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
步骤4:B 完成创建
java
// B 获取到 A 的早期引用后,完成属性注入
// 然后完成 B 的初始化(@PostConstruct, InitializingBean等)
// 最后将 B 放入一级缓存
addSingleton("B", B);
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
}
}
步骤5:A 完成创建
java
// A 继续属性注入,现在可以获取到完整的 B
// 完成 A 的初始化
// 将 A 放入一级缓存
addSingleton("A", A);
源码实现细节
关键方法解析
doCreateBean 方法
java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. 判断是否支持早期暴露
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 3. 添加到三级缓存(关键步骤!)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 4. 属性注入(可能触发循环依赖)
Object exposedObject = bean;
populateBean(beanName, mbd, instanceWrapper);
// 5. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 6. 处理早期引用
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
}
return exposedObject;
}
addSingletonFactory 方法
java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 放入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 清除二级缓存
this.earlySingletonObjects.remove(beanName);
}
}
}
特殊情况处理
1. 有 AOP 代理的情况
java
@Component
public class A {
@Autowired
private B b;
public void businessMethod() {
// 业务逻辑
}
}
@Aspect
@Component
public class MyAspect {
@Before("execution(* A.businessMethod(..))")
public void beforeAdvice() {
System.out.println("Before method execution");
}
}
处理机制:
- 在
getEarlyBeanReference
方法中会调用 AOP 相关的后处理器 - 返回代理对象而不是原始对象
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
// AOP 在这里创建代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
2. @Async 方法的情况
java
@Component
public class A {
@Autowired
private B b;
@Async
public void asyncMethod() {
// 异步方法
}
}
@Component
public class B {
@Autowired
private A a;
}
处理 :同样通过 getEarlyBeanReference
方法创建代理
无法解决的循环依赖场景
1. 构造器注入的循环依赖
java
@Component
public class A {
private B b;
@Autowired
public A(B b) { // 构造器注入
this.b = b;
}
}
@Component
public class B {
private A a;
@Autowired
public B(A a) { // 构造器注入
this.a = a;
}
}
原因:在构造器阶段就需要完整的依赖对象,此时 Bean 还未实例化,无法放入三级缓存。
错误信息:
vbnet
BeanCurrentlyInCreationException: Error creating bean with name 'A':
Requested bean is currently in creation: Is there an unresolvable circular reference?
2. 原型作用域的循环依赖
java
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeA {
@Autowired
private PrototypeB b;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeB {
@Autowired
private PrototypeA a;
}
原因:原型 Bean 不缓存,每次都会创建新实例。
3. @Configuration 类的循环依赖
java
@Configuration
public class ConfigA {
@Autowired
private ConfigB configB;
}
@Configuration
public class ConfigB {
@Autowired
private ConfigA configA;
}
解决方案和最佳实践
1. 代码重构方案
方案1:使用 setter 注入代替构造器注入
java
@Component
public class A {
private B b;
@Autowired
public void setB(B b) { // setter注入
this.b = b;
}
}
方案2:使用 @Lazy 注解
java
@Component
public class A {
private final B b;
@Autowired
public A(@Lazy B b) { // 延迟加载
this.b = b;
}
}
方案3:使用 ApplicationContextAware
java
@Component
public class A implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void someMethod() {
// 在需要的时候再获取B
B b = applicationContext.getBean(B.class);
}
}
方案4:重新设计代码结构
java
// 提取公共逻辑到第三个类
@Component
public class ServiceFacade {
@Autowired
private A a;
@Autowired
private B b;
public void commonBusiness() {
a.doSomething();
b.doSomething();
}
}
2. Spring 配置方案
开启循环依赖支持(默认开启)
java
@Configuration
public class AppConfig {
// Spring Boot 2.6+ 默认关闭循环依赖
// 需要手动开启
@Bean
public AbstractBeanFactory abstractBeanFactory() {
return new DefaultListableBeanFactory() {
{
setAllowCircularReferences(true);
}
};
}
}
调试和排查技巧
1. 查看循环依赖信息
java
@Component
public class CircularDependencyDetector implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (applicationContext instanceof ConfigurableApplicationContext) {
ConfigurableListableBeanFactory beanFactory =
((ConfigurableApplicationContext) applicationContext).getBeanFactory();
if (beanFactory instanceof DefaultListableBeanFactory) {
// 可以查看三级缓存状态
System.out.println("检测循环依赖...");
}
}
}
}
2. 使用 Spring Boot Actuator
yaml
management:
endpoints:
web:
exposure:
include: beans
endpoint:
beans:
enabled: true
访问:http://localhost:8080/actuator/beans
查看 Bean 依赖关系
总结
Spring 通过三级缓存机制优雅地解决了循环依赖问题:
-
三级缓存分工明确:
- 一级缓存:完整的 Bean
- 二级缓存:早期的 Bean(避免重复创建代理)
- 三级缓存:ObjectFactory(创建早期引用)
-
解决条件:
- 必须是单例 Bean
- 不能是构造器注入
- 必须通过 setter/字段注入
-
设计思想:
- 提前暴露引用
- 分离实例化、依赖注入、初始化阶段
- 支持 AOP 等增强功能
理解 Spring 的循环依赖解决机制,有助于编写更健壮的代码和更好地排查相关问题。
4️⃣ 参考 4
Spring 如何解决循环依赖
1. 循环依赖的定义和类型
1.1 什么是循环依赖 循环依赖是指两个或多个 Bean 相互依赖,形成闭环:
- A 依赖 B
- B 依赖 A
或者更复杂的:
- A 依赖 B
- B 依赖 C
- C 依赖 A
1.2 循环依赖的类型
java
// 1. 字段注入循环依赖(Spring 可以解决)
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
// 2. setter 方法注入循环依赖(Spring 可以解决)
@Component
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
// 3. 构造器注入循环依赖(Spring 无法解决)
@Component
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
2. Spring 解决循环依赖的核心机制
2.1 三级缓存架构
Spring 通过三级缓存解决循环依赖问题:
java
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期 Bean(已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:ObjectFactory(用于创建早期引用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 正在创建中的 Bean
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}
2.2 解决流程详细分析
示例场景:ServiceA ↔ ServiceB 循环依赖
java
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void methodA() {
System.out.println("ServiceA method");
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void methodB() {
System.out.println("ServiceB method");
}
}
解决步骤:
-
开始创建 ServiceA
java// 标记 A 正在创建 singletonsCurrentlyInCreation.add("serviceA"); // 实例化 ServiceA(调用构造函数) ServiceA a = new ServiceA(); // 将 ServiceA 的 ObjectFactory 放入三级缓存 addSingletonFactory("serviceA", () -> getEarlyBeanReference("serviceA", mbd, a));
-
ServiceA 属性注入,发现需要 ServiceB
java// 在 populateBean 阶段,发现 @Autowired ServiceB // 调用 getBean("serviceB") 获取 ServiceB
-
开始创建 ServiceB
java// 标记 B 正在创建 singletonsCurrentlyInCreation.add("serviceB"); // 实例化 ServiceB ServiceB b = new ServiceB(); // 将 ServiceB 的 ObjectFactory 放入三级缓存 addSingletonFactory("serviceB", () -> getEarlyBeanReference("serviceB", mbd, b));
-
ServiceB 属性注入,发现需要 ServiceA
java// 调用 getBean("serviceA"),发现 serviceA 正在创建 // 从三级缓存获取 ServiceA 的早期引用 ObjectFactory<?> singletonFactory = this.singletonFactories.get("serviceA"); if (singletonFactory != null) { // 创建早期引用(可能经过 AOP 处理) Object earlyReference = singletonFactory.getObject(); // 将早期引用放入二级缓存,移除三级缓存 this.earlySingletonObjects.put("serviceA", earlyReference); this.singletonFactories.remove("serviceA"); // ServiceB 成功注入 ServiceA 的早期引用 }
-
ServiceB 完成创建
java// ServiceB 完成属性注入、初始化等后续步骤 // 将完整的 ServiceB 放入一级缓存 addSingleton("serviceB", b); // 清理缓存 this.earlySingletonObjects.remove("serviceB"); this.singletonFactories.remove("serviceB");
-
ServiceA 完成创建
java// ServiceA 继续属性注入(此时 ServiceB 已可用) // ServiceA 完成初始化 // 将完整的 ServiceA 放入一级缓存 addSingleton("serviceA", a); // 清理缓存 this.earlySingletonObjects.remove("serviceA"); this.singletonFactories.remove("serviceA");
3. 源码关键方法解析
3.1 getSingleton 方法(核心查找逻辑)
java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存查找完整 Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 从二级缓存查找早期 Bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 创建早期引用
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3.2 doCreateBean 方法(创建流程)
java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化
Object beanInstance = createBeanInstance(beanName, mbd, args);
// 2. 判断是否允许早期暴露
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 3. 添加到三级缓存(解决循环依赖的关键)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
}
// 4. 属性注入(可能触发循环依赖)
populateBean(beanName, mbd, instanceWrapper);
// 5. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
3.3 addSingletonFactory 方法
java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
}
}
}
4. 特殊情况处理
4.1 AOP 代理对象的循环依赖
java
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional
public void createUser() {
// 业务逻辑
}
}
@Service
public class OrderService {
@Autowired
private UserService userService; // 需要的是 UserService 的代理对象
}
处理机制:
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
// AbstractAutoProxyCreator 会在这里返回代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
4.2 多级循环依赖
java
@Service
public class ServiceA {
@Autowired private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired private ServiceC serviceC;
}
@Service
public class ServiceC {
@Autowired private ServiceA serviceA; // 三级循环依赖
}
处理流程:
- A 创建 → 需要 B
- B 创建 → 需要 C
- C 创建 → 需要 A(从三级缓存获取 A 的早期引用)
- C 完成 → B 完成 → A 完成
5. Spring 无法解决的循环依赖场景
5.1 构造器注入循环依赖
java
@Component
public class CircularA {
private final CircularB circularB;
@Autowired
public CircularA(CircularB circularB) { // 构造器注入
this.circularB = circularB;
}
}
@Component
public class CircularB {
private final CircularA circularA;
@Autowired
public CircularB(CircularA circularA) { // 构造器注入
this.circularA = circularA;
}
}
错误信息:
vbnet
BeanCurrentlyInCreationException:
Error creating bean with name 'circularA':
Requested bean is currently in creation: Is there an unresolvable circular reference?
原因:构造器注入在实例化阶段就需要完整依赖,此时 Bean 还未放入三级缓存。
5.2 Prototype 作用域的循环依赖
java
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeA {
@Autowired
private PrototypeB prototypeB;
}
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeB {
@Autowired
private PrototypeA prototypeA;
}
原因:Prototype Bean 不缓存,每次都会新建,无法通过缓存机制解决循环依赖。
5.3 @Async 方法的循环依赖
java
@Service
public class AsyncServiceA {
@Autowired
private AsyncServiceB serviceB;
@Async
public void asyncMethod() {
// 异步方法
}
}
@Service
public class AsyncServiceB {
@Autowired
private AsyncServiceA serviceA; // 可能存在问题
}
6. 解决方案和最佳实践
6.1 避免循环依赖的设计
java
// 好的设计:使用接口解耦
public interface UserValidator {
boolean validate(User user);
}
@Service
public class BasicUserValidator implements UserValidator {
// 不依赖其他业务Service
}
@Service
public class UserService {
@Autowired
private List<UserValidator> validators; // 依赖接口而非具体实现
}
6.2 使用 @Lazy 注解
java
@Service
public class ServiceA {
@Lazy // 延迟注入,打破循环
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
6.3 使用 setter 注入 + @Autowired(required = false)
java
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired(required = false)
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
6.4 使用 ApplicationContextAware
java
@Service
public class ServiceA implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void doSomething() {
ServiceB serviceB = applicationContext.getBean(ServiceB.class);
// 使用 serviceB
}
}
7. 总结
Spring 通过三级缓存机制优雅地解决了循环依赖问题:
- 一级缓存:存储完整的单例 Bean
- 二级缓存:存储早期的 Bean 引用
- 三级缓存:存储 Bean 工厂,用于创建早期引用(支持 AOP 代理)
关键点:
- 只有单例 Bean + setter/字段注入才能解决循环依赖
- 构造器注入的循环依赖无法解决
- Prototype Bean的循环依赖无法解决
- 合理设计架构,避免循环依赖是最佳实践
设计启示:Spring 的三级缓存体现了"空间换时间"和"关注点分离"的设计思想,通过缓存分层解决了复杂的依赖关系问题。
5️⃣ 参考 5
Spring 通过 三级缓存机制 和 提前暴露对象引用 的方式,优雅地解决了单例 Bean 之间的循环依赖问题。以下是其核心原理和详细流程:
一、循环依赖的场景 当两个或多个 Bean 相互依赖时,会形成循环依赖:
- 场景示例 :
- Bean A 依赖 Bean B(
A → B
) - Bean B 依赖 Bean A(
B → A
)
- Bean A 依赖 Bean B(
- 问题:如果按顺序创建 Bean,会导致死锁(A 等待 B 初始化完成,B 又等待 A 初始化完成)。
二、Spring 解决循环依赖的核心机制 Spring 通过 三级缓存 和 提前暴露对象引用 打破循环依赖的死锁:
1. 三级缓存的结构
缓存层级 | 存储内容 | 作用 |
---|---|---|
一级缓存 | SingletonObjects (完全初始化的 Bean) |
存储已完整初始化的单例 Bean,可直接使用。 |
二级缓存 | EarlySingletonObjects (半成品 Bean) |
存储已实例化但未完全初始化的 Bean(属性未注入),用于循环依赖场景。 |
三级缓存 | SingletonFactories (ObjectFactory) |
存储 Bean 的工厂对象,用于生成代理对象或原始对象(解决 AOP 代理问题)。 |
2. 关键流程 以 A → B → A 的循环依赖为例,说明 Spring 的处理步骤:
(1)创建 Bean A
- Spring 从一级缓存查找 Bean A,未找到。
- 创建 Bean A 的实例(调用构造器),并将其包装成
ObjectFactory
存入 三级缓存。 - 开始属性注入,发现依赖 Bean B。
(2)创建 Bean B
- Spring 从一级缓存查找 Bean B,未找到。
- 创建 Bean B 的实例,并将其包装成
ObjectFactory
存入 三级缓存。 - 开始属性注入,发现依赖 Bean A。
(3)解决 Bean A 的循环依赖
- Spring 从一级缓存查找 Bean A,未找到;从二级缓存查找,未找到。
- 从 三级缓存 获取 Bean A 的
ObjectFactory
,调用getObject()
生成 半成品 Bean A(可能是代理对象)。 - 将半成品 Bean A 存入 二级缓存,并从三级缓存移除。
- 将半成品 Bean A 注入 Bean B。
(4)完成 Bean B 的初始化
- Bean B 完成属性注入和初始化(如调用
init-method
)。 - 将完全初始化的 Bean B 存入 一级缓存,并从二级缓存移除。
(5)完成 Bean A 的初始化
- Spring 继续 Bean A 的属性注入,从一级缓存获取完全初始化的 Bean B。
- Bean A 完成初始化,存入 一级缓存,并从二级缓存移除。
三、为什么需要三级缓存? 1. 解决 AOP 代理问题
- 如果 Bean 需要被代理(如
@Transactional
、@Async
),三级缓存的ObjectFactory
可以按需生成 代理对象 或 原始对象。 - 例如:Bean A 需要被代理,工厂对象会返回代理对象而非原始对象。
2. 避免重复创建
- 三级缓存通过
ObjectFactory
延迟对象生成,确保单例 Bean 的唯一性。 - 如果直接存储半成品 Bean(二级缓存),可能无法处理代理场景。
3. 性能优化
- 半成品 Bean 仅在需要时生成(如循环依赖时),减少不必要的初始化开销。
四、源码解析 关键方法位于 DefaultSingletonBeanRegistry
类中:
1. 获取单例 Bean( getSingleton )
java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取工厂对象,生成半成品 Bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
2. 添加工厂对象到三级缓存( addSingletonFactory )
java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 将工厂对象存入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 标记 Bean 正在创建中
this.registeredSingletons.add(beanName);
}
}
}
五、限制与注意事项 1. 仅支持单例 Bean
- 多例(Prototype)Bean 无法通过三级缓存解决循环依赖,因为每次请求都会创建新实例。
2. 构造器注入的局限性
- 如果循环依赖通过 构造器注入 (如
public A(B b) {}
),三级缓存无法解决,会抛出BeanCurrentlyInCreationException
。 - 解决方案 :改用 Setter 注入 或 字段注入。
3. 代理对象的兼容性
- 如果 Bean 未被代理,三级缓存可简化为二级缓存(直接存储半成品 Bean)。
- 但 Spring 为统一处理代理场景,始终使用三级缓存。
六、总结
机制 | 作用 |
---|---|
三级缓存 | 分阶段存储 Bean 的不同状态(原始对象、半成品、完全初始化)。 |
提前暴露引用 | 通过三级缓存的工厂对象,在 Bean 初始化完成前暴露其引用,打破循环。 |
AOP 代理支持 | 工厂对象按需生成代理对象,确保代理逻辑正确执行。 |
Spring 的三级缓存机制通过 分阶段缓存 和 延迟对象生成,高效地解决了单例 Bean 的循环依赖问题,同时兼容 AOP 代理场景。理解这一机制有助于优化 Bean 设计,避免不必要的循环依赖。
6️⃣ 参考 6
Spring如何解决循环依赖
Spring通过三级缓存机制来解决循环依赖问题,这是Spring IoC容器的核心设计之一。下面我将详细解释Spring解决循环依赖的原理和流程。
三级缓存的组成
Spring的三级缓存包括:
-
一级缓存 (singletonObjects)
- 存储完全初始化完成的单例Bean
- 是最终可以被使用的Bean实例
- 通过
ConcurrentHashMap
实现
-
二级缓存 (earlySingletonObjects)
- 存储已实例化但未初始化的"早期引用"(半成品Bean)
- 用于解决AOP代理问题
- 通过
HashMap
实现
-
三级缓存 (singletonFactories)
- 存储
ObjectFactory
函数式接口 - 用于在Bean创建过程中提前暴露未完成的Bean
- 通过
Map<String, ObjectFactory<?>>
实现
- 存储
解决循环依赖的核心原理
Spring解决循环依赖的关键在于提前暴露未完全创建完毕的Bean,而不是等待Bean完全初始化后再处理依赖。
重要限制 :Spring仅能解决Setter注入 方式的循环依赖,不能解决构造器注入的循环依赖。在Spring Boot 3.x中,已不再允许处理构造函数的循环依赖。
解决循环依赖的流程(以A依赖B,B依赖A为例)
-
创建A:
- Spring先实例化A(A是半成品,属性未注入)
- 将A的ObjectFactory放入三级缓存(singletonFactories)
-
A需要注入B:
- 开始实例化B
- 在B的创建过程中,需要注入A
-
B查找A:
- B在一级缓存中找不到A
- 在二级缓存中也找不到A
- 从三级缓存中获取A的ObjectFactory
- 通过ObjectFactory创建A的半成品对象
- 将A的半成品对象放入二级缓存(earlySingletonObjects)
-
B完成初始化:
- B完成属性注入和初始化
- B放入一级缓存(singletonObjects)
-
A继续完成:
- A从二级缓存中获取到B
- A完成属性注入和初始化
- A放入一级缓存
- 从三级缓存中移除A的ObjectFactory
为什么需要三级缓存?
- 如果只有两级缓存(一级和二级),在AOP场景下会导致问题
- 三级缓存提供了一个"工厂",可以在需要时生成代理对象
- 保证了在Bean创建过程中可以提前暴露半成品,解决循环依赖
重要注意事项
-
适用范围:
- 仅适用于Singleton作用域的Bean
- 无法解决构造器注入的循环依赖
- 无法解决原型Bean(Prototype)的循环依赖
-
AOP代理处理:
- 在
getEarlyBeanReference
方法中,如果有AOP配置,会在此生成代理对象 - 三级缓存允许在Bean创建过程中提前生成代理
- 在
-
设计建议:
- Spring仍然鼓励避免循环依赖,因为它会增加代码复杂性
- 应尽量设计应用结构,避免出现循环依赖
为什么不能用构造器注入解决循环依赖?
构造器注入是在Bean实例化时完成的,而循环依赖需要在实例化后才能解决。当两个Bean都使用构造器注入对方时,Spring无法解决这种循环依赖,会抛出BeanCurrentlyInCreationException
异常。
总结
Spring的三级缓存机制通过"提前暴露半成品Bean"的方式,解决了Setter注入方式下的循环依赖问题。它在Bean创建过程中,将未完全初始化的Bean提前暴露,使依赖方可以获取到这个"半成品",从而打破循环依赖。这种设计保证了Bean创建过程的一致性和正确性,是Spring IoC容器的重要特性。
提示 :虽然Spring能解决循环依赖,但循环依赖通常表明设计问题,应尽量避免。如果必须使用循环依赖,建议使用
@Lazy
注解或重构代码结构来避免。
7️⃣ 参考 7
Spring 主要通过其 三级缓存机制 来解决单例(Singleton)Bean 之间的循环依赖问题。这是 Spring IoC 容器的核心设计之一,确保了在复杂的依赖关系下 Bean 能够被正确创建。
核心前提
Spring 的循环依赖解决方案仅适用于单例(Singleton)作用域的 Bean ,并且是通过setter 注入 或字段注入 形成的循环依赖。对于构造器注入的循环依赖,或者原型(Prototype)作用域的循环依赖,Spring 无法解决,会直接抛出 BeanCurrentlyInCreationException
异常。
最常见的循环依赖场景是两个 Bean 相互依赖:
AService
依赖BService
BService
依赖AService
解决方案:三级缓存
Spring 使用三个 Map 结构(即"三级缓存")来协同工作:
-
一级缓存 (
singletonObjects
):- 存放完全初始化完成的单例 Bean 实例。
- 这是最终的、可直接使用的 Bean 仓库。
-
二级缓存 (
earlySingletonObjects
):- 存放提前暴露的、原始的 Bean 实例。
- 这些实例已经通过构造函数创建(实例化),但尚未进行属性填充和初始化。
-
三级缓存 (
singletonFactories
):- 存放能够创建早期 Bean 实例的工厂(
ObjectFactory
)。 - 这个工厂的核心作用是在需要时生成 Bean 的早期引用,特别关键的是它能处理 AOP 代理的创建。
- 存放能够创建早期 Bean 实例的工厂(
工作流程详解
以 AService
和 BService
循环依赖为例:
-
开始创建
AService
- Spring 检查一级、二级缓存都不存在
AService
。 - 开始创建
AService
,调用其构造函数,实例化完成。 - 此时
AService
对象已存在,但它的属性(如bService
)还未注入。
- Spring 检查一级、二级缓存都不存在
-
暴露
AService
的早期引用- 为了打破可能的循环依赖,Spring 将一个
ObjectFactory
(工厂)放入三级缓存。 - 这个工厂的作用是:当被调用时,可以返回
AService
的早期引用。如果AService
需要 AOP 代理,这个工厂会在这里创建代理对象。
- 为了打破可能的循环依赖,Spring 将一个
-
注入
AService
的依赖 (BService
)AService
需要注入BService
,于是 Spring 开始创建BService
。BService
经历同样的过程:实例化完成,并将其ObjectFactory
放入三级缓存。
-
BService
需要注入AService
(循环点)- 当为
BService
注入AService
时,Spring 查找AService
。 - 一级、二级缓存都没有,但在三级缓存 中找到了
AService
的ObjectFactory
。 - Spring 调用这个工厂的
getObject()
方法。 - 工厂方法内部执行
getEarlyBeanReference()
:- 如果
AService
需要代理(如@Transactional
),则在此刻创建代理对象。 - 如果不需要,则返回原始的
AService
实例。
- 如果
- 将得到的早期引用(可能是代理对象)放入二级缓存。
- 从三级缓存中移除
AService
的ObjectFactory
(保证只创建一次)。 - 将这个早期引用注入到
BService
中。
- 当为
-
完成
BService
的创建BService
完成所有属性的注入和初始化回调。- 将完整的
BService
放入一级缓存。 - 从二级、三级缓存中清理
BService
的相关条目。
-
完成
AService
的创建- 回到
AService
的创建流程,此时BService
已经创建完毕。 - 将
BService
注入到AService
中。 AService
完成初始化,成为一个完整的 Bean。- 将
AService
放入一级缓存。 - 清理其在二级缓存中的早期引用。
- 回到
为什么需要三级缓存?二级不够吗?
这是关键所在。如果只有二级缓存,在 AService
实例化后就直接把原始对象放进二级缓存。当 BService
需要时,会拿到这个原始对象。
但如果 AService
是一个需要事务管理的 Service,那么最终容器里应该是一个代理对象。这就导致了不一致:
BService
持有的是AService
的原始对象。- 容器最终提供的是
AService
的代理对象。
这破坏了单例原则,也使得 AOP 功能失效。
三级缓存的精妙之处在于 : 它不直接存放对象,而是存放一个工厂 。这个工厂可以在被调用时动态决定返回原始对象还是代理对象。这样,无论是早期注入还是最终使用,所有地方引用的都是同一个最终对象(要么都是原始对象,要么都是代理对象),保证了对象一致性。
总结
Spring 通过 三级缓存 解决单例 Bean 的循环依赖:
- 在 Bean 实例化后 ,立即向三级缓存注册一个
ObjectFactory
。 - 当出现循环依赖时,通过三级缓存的工厂获取早期引用,并将其放入二级缓存。
- 最终 Bean 创建完成后,放入一级缓存,并清理低级缓存。
这一机制不仅解决了循环依赖,还巧妙地与 AOP 集成,确保了代理对象的一致性,体现了 Spring 设计的高超之处。
8️⃣ 参考 8
Spring解决循环依赖的核心机制是三级缓存 ,但这一机制仅针对单例Bean的setter注入/字段注入场景。循环依赖指多个Bean之间互相依赖(如A依赖B,B依赖A),若不处理会导致创建过程陷入无限循环。以下是Spring解决循环依赖的具体逻辑:
一、循环依赖的分类与Spring的处理范围 首先明确Spring能解决和不能解决的循环依赖类型:
- 能解决 :单例Bean之间通过setter方法注入 或字段注入形成的循环依赖。
- 不能解决 :
- 原型Bean(
scope="prototype"
)的循环依赖(每次创建都是新实例,无法缓存); - 单例Bean之间通过构造器注入形成的循环依赖(实例化前无法提前暴露对象)。
- 原型Bean(
二、核心解决方案:三级缓存的协同工作 Spring通过三级缓存(按"Bean完成度"从高到低)实现单例Bean循环依赖的破解,核心是**"提前暴露未完全初始化的Bean"**,让依赖方先拿到引用,后续再完善Bean。
- 三级缓存的定义
- 一级缓存(singletonObjects) :存储完全初始化完成的单例Bean(已实例化、属性注入、初始化方法执行完毕),是最终供使用的Bean。
- 二级缓存(earlySingletonObjects) :存储提前暴露的早期Bean(已实例化,但未完成属性注入和初始化),可能是原始对象或AOP代理对象。
- 三级缓存(singletonFactories) :存储Bean的工厂对象(ObjectFactory),用于"按需生成"早期Bean(若需要AOP代理,工厂会生成代理对象;否则返回原始对象)。
- 解决循环依赖的流程(以A依赖B,B依赖A为例) 假设A和B都是单例Bean,且通过setter注入依赖:
步骤1:创建Bean A
- 检查一级缓存(A不存在),开始创建A。
- 实例化A(调用构造器生成原始对象,此时A未注入属性、未执行初始化方法)。
- 将A的工厂对象(ObjectFactory)放入三级缓存:工厂逻辑为"若A需要AOP代理,则生成代理对象;否则返回原始A"。
- 开始为A注入属性,发现依赖B,触发Bean B的创建。
步骤2:创建Bean B
- 检查一级缓存(B不存在),开始创建B。
- 实例化B,将B的工厂对象放入三级缓存。
- 为B注入属性,发现依赖A,开始查找A。
步骤3:B获取A的早期实例,打破循环
- 查找A时,一级缓存(A未完成)和二级缓存(无)均无,最终从三级缓存找到A的工厂。
- 通过A的工厂生成A的早期实例(若A需要代理,此处生成代理对象),并将该实例从三级缓存移至二级缓存(标记为"已提前暴露")。
- 将A的早期实例注入B,B完成属性注入和初始化,放入一级缓存(此时B是完整的)。
步骤4:A完成创建
- B创建完成后,回到A的属性注入步骤,从一级缓存取出B并注入A。
- A完成属性注入和初始化,放入一级缓存(此时A是完整的),同时从二级缓存移除A的早期实例。
三、为什么需要三级缓存? 核心是为了处理AOP代理场景下的循环依赖:
- 若仅用两级缓存(一级+二级),提前暴露的只能是A的原始对象。但如果A需要AOP代理(如被
@Transactional
注解),最终容器中的A是代理对象,而B注入的却是原始A,会导致对象不一致。 - 三级缓存的工厂(ObjectFactory)实现了"延迟生成代理":只有当循环依赖发生时,才会通过工厂生成代理对象并放入二级缓存,既保证了注入的是正确的代理对象,又避免了无循环依赖时不必要的代理创建。
四、无法解决的循环依赖场景
-
构造器循环依赖 :
若A的构造器依赖B,B的构造器依赖A,由于实例化(构造器调用)是创建Bean的第一步,此时A和B均未实例化,无法生成工厂对象放入三级缓存,因此会直接抛出
BeanCurrentlyInCreationException
。 -
原型Bean循环依赖 :
原型Bean的
scope
为prototype
,每次获取都会创建新实例,Spring不会缓存原型Bean,因此循环依赖时会无限创建新实例,最终报错。
总结 Spring解决单例Bean循环依赖的核心是三级缓存+提前暴露未完全初始化的Bean:通过三级缓存延迟处理AOP代理,二级缓存暂存早期实例,一级缓存存储最终完整Bean,三者协同打破了"先有鸡还是先有蛋"的循环。但该机制仅适用于单例Bean的setter/字段注入,对构造器注入和原型Bean无效。
9️⃣ 参考 9
Spring通过三级缓存机制高效地解决了单例Bean的循环依赖问题,同时AOP代理的正确性。以下是其核心原理和详细流程:
一、循环依赖的类型
-
构造器循环依赖
- 例如:
A
的构造器依赖B
,B
的构造器又依赖A
。 - Spring无法解决 :因为构造器注入时Bean尚未实例化,无法提前暴露引用。需通过
@Lazy
注解或重构代码避免。
- 例如:
-
属性循环依赖
- 例如:
A
的属性依赖B
,B
的属性又依赖A
。 - Spring可解决:通过三级缓存暴露半成品Bean,允许属性注入。
- 例如:
二、三级缓存机制 Spring通过三个缓存层级管理Bean的不同状态,解决属性循环依赖:
缓存层级 | 存储内容 | 作用 |
---|---|---|
一级缓存 | 完全初始化的Bean(SingletonObjects ) |
存储最终可用的Bean,直接返回给调用者。 |
二级缓存 | 半成品Bean(EarlySingletonObjects ) |
存储已实例化但未初始化的Bean,供循环依赖时提前使用。 |
三级缓存 | Bean工厂(SingletonFactories ) |
存储ObjectFactory ,用于动态生成Bean(如处理AOP代理时的原始Bean或代理Bean)。 |
三、解决循环依赖的核心流程 以A
依赖B
,B
依赖A
为例:
1. 创建Bean A
-
检查一级缓存 :未找到
A
,继续创建。 -
实例化A :调用构造器生成
A
的原始对象(未注入属性)。 -
将A的工厂放入三级缓存 :
javaaddSingletonFactory("A", () -> getEarlyBeanReference("A", beanA, mbd));
getEarlyBeanReference
可能返回代理对象(如AOP场景),确保代理的唯一性。
2. 填充A的属性(发现依赖B)
- 检查一级缓存 :未找到
B
,继续创建。 - 实例化B :调用构造器生成
B
的原始对象。 - 将B的工厂放入三级缓存。
- 填充B的属性(发现依赖A) :
- 从三级缓存获取
A
的工厂,调用getObject()
生成A
的早期引用(可能是代理对象)。 - 将
A
的早期引用从三级缓存移除,并放入二级缓存。 B
完成属性注入(使用二级缓存中的A
)。
- 从三级缓存获取
3. 完成B的初始化
- 初始化B :调用
init-method
、@PostConstruct
等。 - 将B放入一级缓存 :后续获取
B
直接从此缓存返回。 - 从二级缓存移除A的早期引用(若未被其他依赖使用)。
4. 完成A的初始化
- 继续初始化A :使用一级缓存中的
B
完成属性注入。 - 初始化A :调用
init-method
、@PostConstruct
等。 - 将A放入一级缓存 :后续获取
A
直接从此缓存返回。
四、关键代码解析 1. getSingleton 方法
java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 2. 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取工厂并生成早期引用
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 将早期引用从三级缓存移除,并放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
2. addSingletonFactory 方法
java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 将工厂放入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 标记Bean正在创建中
this.registeredSingletons.add(beanName);
}
}
}
五、为什么需要三级缓存?
-
解决AOP代理问题
- 若Bean需要被代理(如
@Transactional
),三级缓存的工厂机制确保代理对象在循环依赖中也能正确生成。 - 例如:
A
被代理为AProxy
,三级缓存的工厂会返回AProxy
,而非原始A
。
- 若Bean需要被代理(如
-
避免重复创建
- 二级缓存存储半成品Bean,防止循环依赖时重复实例化。
-
线程安全
- 一级缓存使用
ConcurrentHashMap
,二级/三级缓存通过同步块保证多线程环境下的正确性。
- 一级缓存使用
六、限制与注意事项
-
仅支持单例Bean
- 原型(
prototype
)Bean无法使用三级缓存,因为每次请求都会创建新实例。
- 原型(
-
构造器注入无效
- 构造器循环依赖需通过
@Lazy
注解或重构代码解决。
- 构造器循环依赖需通过
-
依赖顺序敏感
- 循环依赖的Bean必须按特定顺序初始化(如
A
依赖B
时,B
不能先初始化完成)。
- 循环依赖的Bean必须按特定顺序初始化(如
七、总结 Spring通过三级缓存机制高效解决了属性循环依赖问题:
- 一级缓存存储完全初始化的Bean。
- 二级缓存存储半成品Bean,供循环依赖时提前使用。
- 三级缓存存储工厂,动态生成Bean(如代理对象),确保全局唯一性。
这一设计兼顾了性能、线程安全和AOP兼容性,是Spring框架的核心特性之一。
🔟 参考 10
以下是Spring解决循环依赖的核心方法及技术细节,基于三级缓存机制实现:
一、三级缓存机制(核心解决方案)
-
一级缓存
singletonObjects
✅ 存储完全初始化的单例Bean,可直接使用
javaprivate final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
-
二级缓存
earlySingletonObjects
⚠️ 存储提前暴露的Bean半成品(已实例化但未完成属性注入)
javaprivate final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
-
三级缓存
singletonFactories
🔧 存储Bean工厂对象(用于生成代理对象)
javaprivate final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
二、解决流程(以A→B→A循环依赖为例)
-
创建Bean A
- 实例化A(调用构造器)
- 将A的工厂对象 放入三级缓存
singletonFactories
- 开始属性注入(此时发现依赖B)
-
创建Bean B
- 实例化B → 属性注入时发现依赖A
- 从三级缓存获取A的工厂 → 调用
getObject()
生成A的代理对象 - 将代理对象放入二级缓存
earlySingletonObjects
,同时移除三级缓存的工厂 - B完成初始化,移入一级缓存
-
完成A的注入
- 将二级缓存中的A代理对象注入B
- B返回给A的注入流程
- A完成属性注入 → 移入一级缓存
singletonObjects
三、关键限制条件
-
必须都是单例Bean
javaif (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // 原型模式直接报错
-
不能全是构造器注入
- 构造器注入在实例化阶段就需要完整依赖,无法通过提前暴露对象解决
- 字母序靠前的Bean必须使用setter注入(如A依赖B,则A不能是构造器注入)
四、替代解决方案
-
Setter/字段注入
✅ 允许先创建实例再注入依赖,打破实例化死锁
-
@Lazy注解
java@Autowired public A(@Lazy B b) { ... } // 延迟初始化代理对象
-
ObjectFactory
java@Autowired private ObjectFactory<B> bFactory; public void useB() { B b = bFactory.getObject(); // 按需获取Bean }
五、典型异常场景
log
BeanCurrentlyInCreationException:
Requested bean is currently in creation:
Is there an unresolvable circular reference?
⚠️ 触发条件:
- 原型Bean的循环依赖
- 纯构造器注入的循环依赖
- 通过
@DependsOn
强制指定的循环初始化顺序
提示:Spring Boot 2.6+ 默认禁止循环依赖,可通过
spring.main.allow-circular-references=true
开启。
🤔 参考 11
Spring 解决循环依赖的核心机制是 三级缓存,它通过分层缓存和提前暴露不完全初始化的 Bean 对象,巧妙地打破了循环引用。同时,Spring 也提供了一些注解和编程手段来辅助处理特定场景。下面这张表格汇总了 Spring 三级缓存的核心信息,帮助你快速理解:
缓存层级 | 名称 | 存储内容 | 主要作用 |
---|---|---|---|
一级缓存 | singletonObjects |
完全初始化完成的单例Bean | 提供最终可用的Bean,是正式的"单例池" |
二级缓存 | earlySingletonObjects |
早期暴露的Bean(已实例化,但未完成初始化) | 解决循环依赖,避免多次从工厂创建实例 |
三级缓存 | singletonFactories |
创建Bean的工厂对象 (ObjectFactory ) |
用于延迟创建早期引用,支持AOP代理 |
Spring 解决循环依赖的核心流程,可以概括为 "实例化 -> 提前暴露 -> 依赖注入 -> 完成初始化" 的过程。下面的流程图描绘了这一核心过程:
getBean(a)"] --> B["实例化A
(调用构造函数)"] B --> C[得到A的原始对象] C --> D["提前暴露A的引用
将 ObjectFactory 放入三级缓存"] D --> E["属性注入:A 依赖 B
触发 getBean(b)"] E --> F["创建Bean B
getBean(b)"] F --> G["实例化B
(调用构造函数)"] G --> H[得到B的原始对象] H --> I["提前暴露B的引用
将 ObjectFactory 放入三级缓存"] I --> J["属性注入:B 依赖 A
触发 getBean(a)"] J --> K{"再次查找A
一级缓存? --> 无
二级缓存? --> 无"} K -- 三级缓存有 --> L["通过ObjectFactory
获取A的早期引用"] L --> M["将A的早期引用放入二级缓存
并从三级缓存移除工厂"] M --> N["B注入A的早期引用成功"] N --> O["B完成初始化
放入一级缓存"] O --> P["回到A的属性注入流程
此时成功获取到完整的B"] P --> Q["A完成初始化"] Q --> R["将A放入一级缓存
并清理二、三级缓存"] R --> S["容器中存在
完全初始化的A和B"]
⚙️ 辅助解决方案与适用场景
三级缓存是Spring的默认机制,但开发者也可以通过一些方式主动处理或避免循环依赖:
-
使用
@Lazy
注解@Lazy
注解通过延迟加载来打破循环。它对构造函数注入尤为有效。java@Service public class ServiceA { private ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { // 此时注入的是代理对象 this.serviceB = serviceB; } }
-
使用 Setter/字段注入替代构造器注入 Spring 默认只能解决单例模式下通过 Setter 方法或字段属性注入(
@Autowired
)形成的循环依赖 ,而无法解决构造器注入导致的循环依赖。因为构造器注入发生在实例化阶段,此时对象都还未创建,无法提前暴露引用。 -
使用
@PostConstruct
在其中一个 Bean 注入完成后,再通过注解方法设置依赖。java@Service public class ServiceA { @Autowired private ServiceB serviceB; // 其他代码... } @Service public class ServiceB { private ServiceA serviceA; @Autowired public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; } }
-
实现
ApplicationContextAware
和InitializingBean
这是一种更手动的方式,让 Bean 主动从 Spring 上下文中获取依赖。java@Service public class ServiceA implements ApplicationContextAware, InitializingBean { private ApplicationContext context; private ServiceB serviceB; @Override public void setApplicationContext(ApplicationContext context) { this.context = context; } @Override public void afterPropertiesSet() { // 在所有属性设置完成后,再从容器中获取ServiceB this.serviceB = context.getBean(ServiceB.class); } }
⚠️ 注意事项与限制
Spring 的解决方案虽然强大,但并非万能,存在以下限制:
- 必须是单例(Singleton)Bean :原型(Prototype)作用域的 Bean 无法利用三级缓存解决循环依赖。
- 不支持构造器循环依赖 :如果循环依赖是通过构造器注入(Constructor Injection)方式发生的,三级缓存也无法解决。
- 设计警示 :虽然 Spring 提供了这些机制,但从设计角度来说,应用程序中出现循环依赖通常是不推荐的,它可能意味着代码的职责划分不够清晰。
💡 最佳实践 如果条件允许,优先考虑通过代码重构(如提取公共逻辑到第三个类、使用接口分离职责等)来消除循环依赖,这往往是最根本的解决方案。
💎 总结
Spring 主要通过三级缓存机制 自动解决单例 Bean 的 setter/字段注入 导致的循环依赖,其本质是空间换时间,通过提前暴露对象引用打破循环。
对于无法自动处理的场景(如构造器注入或需要更细粒度控制),可以使用 @Lazy
、@PostConstruct
或实现接口等编程方式辅助解决。
理解这些机制和限制,能帮助你在设计和调试时更有把握。但记住,一个良好架构的系统应尽量避免循环依赖。
🤔 参考 12
Spring 通过 三级缓存机制 和 对象生命周期管理 巧妙解决了单例 Bean 的循环依赖问题,其核心设计兼顾了性能、线程安全和 AOP 代理兼容性。以下是具体实现原理和流程解析:
一、循环依赖的本质与场景 循环依赖指 两个或多个 Bean 相互引用形成闭环,例如:
- 属性注入循环 :
A → B → A
- 构造器注入循环 :
A 的构造器依赖 B,B 的构造器依赖 A
Spring 仅支持 单例作用域(Singleton) 的循环依赖解决,原型(Prototype)作用域的 Bean 无法处理。
二、三级缓存的核心设计 Spring 的三级缓存定义在 DefaultSingletonBeanRegistry
类中,各层级作用如下:
缓存层级 | 名称 | 存储内容 | 核心作用 |
---|---|---|---|
一级缓存 | singletonObjects |
完全初始化的单例 Bean(包括依赖注入和 AOP 代理完成后的最终实例) | 直接提供可用的最终 Bean,供外部获取 |
二级缓存 | earlySingletonObjects |
实例化但未完成初始化的 Bean(如构造函数执行完成,但属性注入或初始化方法未执行) | 允许其他 Bean 在循环依赖中获取早期引用,打破循环等待 |
三级缓存 | singletonFactories |
存储生成早期引用的 ObjectFactory 对象 |
支持动态生成代理对象(如 AOP 场景),确保不同依赖链获取的代理对象引用一致 |
三、循环依赖的解决流程(以属性注入为例) 以 A → B → A
为例,流程如下:
-
创建 Bean A
- 实例化 A:通过反射调用构造函数生成原始对象。
- 暴露早期引用 :将
ObjectFactory
存入三级缓存(singletonFactories
),用于后续生成早期引用(可能是代理对象)。 - 依赖注入 B:发现 A 依赖 B,触发 B 的创建流程。
-
创建 Bean B
- 实例化 B:生成原始对象。
- 暴露早期引用 :将 B 的
ObjectFactory
存入三级缓存。 - 依赖注入 A:发现 B 依赖 A,尝试从缓存获取 A 的实例。
-
解决循环依赖
- 从三级缓存获取 A 的工厂 :调用
getObject()
生成 A 的早期引用(可能为代理对象)。 - 存入二级缓存 :将 A 的早期引用移至
earlySingletonObjects
,供 B 注入。 - 完成 B 的初始化:注入 A 的早期引用后,B 完成初始化并存入一级缓存。
- 从三级缓存获取 A 的工厂 :调用
-
完成 A 的初始化
- 注入 B 的最终实例:从一级缓存获取已完成的 B,完成 A 的依赖注入。
- A 存入一级缓存:A 的初始化完成后,移出二级缓存,最终存入一级缓存。
四、关键设计解析 1. 提前暴露半成品对象
- 三级缓存的作用 :在 Bean 实例化后、属性注入前,将
ObjectFactory
暴露到三级缓存,允许其他 Bean 获取未完全初始化的对象。 - 二级缓存的过渡:早期引用从三级缓存转移到二级缓存,避免污染一级缓存(存放完整 Bean)。
- AOP 代理兼容性
- 动态代理生成 :若 Bean 需要 AOP 代理,
singletonFactories
中的ObjectFactory
会调用getEarlyBeanReference()
生成代理对象,确保依赖注入的始终是代理实例。 - 一致性保障:三级缓存通过工厂模式延迟生成代理,避免不同依赖链获取不同代理对象的问题。
- 线程安全与性能优化
- 双重检查锁 :在
getSingleton()
方法中,通过synchronized
和volatile
保证多线程环境下缓存的正确性。 - 懒加载策略:仅在需要时生成早期引用,减少不必要的对象创建。
五、构造器注入的循环依赖问题 Spring 无法通过三级缓存解决构造器注入的循环依赖,原因如下:
- 实例化即依赖注入:构造器注入在 Bean 实例化时就需要依赖对象,此时 Bean 尚未存入缓存。
- 解决方案 :
-
@Lazy
注解 :延迟加载依赖的 Bean,打破循环。java@Service public class A { @Autowired public A(@Lazy B b) { ... } }
-
CGLIB 代理 :通过
@Scope(proxyMode = TARGET_CLASS)
生成代理类,代理对象持有依赖的引用。java@Service @Scope(proxyMode = TARGET_CLASS) public class B { ... }
-
六、设计意图与最佳实践
-
核心目标:
- 解耦依赖链:允许 Bean 在未完全初始化时被引用。
- 兼容 AOP:确保代理对象在依赖注入时的一致性。
- 性能优化:减少重复对象创建和锁竞争。
-
开发建议:
- 优先使用 Setter/字段注入:避免构造器注入导致的循环依赖。
- 合理拆分模块:通过引入中间层(如 Facade 模式)打破循环。
- 谨慎使用
@Lazy
:过度使用可能掩盖设计缺陷。
总结 Spring 的三级缓存机制通过 分层存储 Bean 的不同生命周期状态,结合动态代理技术,实现了对单例 Bean 循环依赖的高效解决。其设计体现了对性能、灵活性和扩展性的深度权衡,是 Spring IoC 容器的核心优势之一。
🤔 参考 13
Spring 通过其独特的三级缓存机制有效解决了循环依赖问题,以下是核心原理及关键步骤:
一、核心前提与限制条件
-
仅适用于单例模式 :循环依赖的两个 Bean 必须均为单例(
singleton
),原型(prototype
)作用域的 Bean 因不存入缓存而无法提前暴露引用[^4^][^5^]。 -
非构造函数注入:若依赖通过构造函数注入,由于实例化阶段需立即满足依赖关系,无法提前暴露半成品对象,导致死循环[^4^][^5^]。
-
Setter/Field 注入方式:依赖需通过 Setter 方法或字段注入,允许延迟注入时机[^3^][^4^]。
二、三级缓存机制解析
-
一级缓存
singletonObjects
- 存储内容:完全初始化完成的单例 Bean(已完成属性注入、初始化及 AOP 代理生成)[^1^][^4^]。
- 作用:提供最终可用的 Bean 实例,避免重复初始化[^1^][^4^]。
-
二级缓存
earlySingletonObjects
- 存储内容:早期暴露的未完成初始化的 Bean 引用(已实例化但未进行属性注入和初始化)[^1^][^4^]。
- 作用:供循环依赖时快速获取半成品对象,打破初始化顺序的限制[^1^][^4^]。
-
三级缓存
singletonFactories
- 存储内容 :生成 Bean 实例的工厂对象(
ObjectFactory
),而非直接存储 Bean[^1^][^4^]。 - 核心作用 :动态决定是否创建 AOP 代理对象。若 Bean 需代理(如
@Transactional
),则通过工厂生成代理而非原始对象[^1^][^4^]。
- 存储内容 :生成 Bean 实例的工厂对象(
三、解决循环依赖的完整流程
以经典的A→B→A依赖链为例:
-
实例化与三级缓存注册
- 当创建 Bean A 时,先实例化 A 的对象,并将其封装为
ObjectFactory
存入三级缓存[^1^][^4^]。此时暂不放入一/二级缓存。
- 当创建 Bean A 时,先实例化 A 的对象,并将其封装为
-
处理依赖 Bean B
- 在为 A 注入依赖 B 时,发现 B 未初始化,触发 B 的创建流程。同样将 B 的
ObjectFactory
存入三级缓存[^1^][^4^]。
- 在为 A 注入依赖 B 时,发现 B 未初始化,触发 B 的创建流程。同样将 B 的
-
检测到循环依赖
- 当创建 B 的过程中试图注入 A 时,从三级缓存中获取 A 的
ObjectFactory
,调用其getObject()
方法生成早期引用[^1^][^4^]。
- 当创建 B 的过程中试图注入 A 时,从三级缓存中获取 A 的
-
升级缓存层级
- 将 A 的早期引用从三级缓存移至二级缓存
earlySingletonObjects
,供 B 完成属性注入[^1^][^4^]。
- 将 A 的早期引用从三级缓存移至二级缓存
-
完成初始化并最终放入一级缓存
- B 完成属性注入后,将其完整实例放入一级缓存
singletonObjects
[^1^][^4^]。 - 随后继续完成 A 的初始化,最终也将 A 放入一级缓存[^1^][^4^]。
- B 完成属性注入后,将其完整实例放入一级缓存
四、关键设计优势
-
双重延迟决策
- 延迟点1:仅在被依赖时才从三级缓存生成早期引用[^1^][^4^]。
- 延迟点2:动态判断是否需要创建 AOP 代理,避免不必要的代理对象生成[^1^][^4^]。
-
状态完整性保障
- 早期引用已通过
populateBean()
完成属性注入,确保代理对象可安全使用属性值[^1^][^4^]。
- 早期引用已通过
-
资源高效利用
- 无循环依赖时不生成代理对象,节省内存;有循环依赖时复用同一代理对象,避免多例冲突[^1^][^4^]。
五、无法解决的场景
-
构造器循环依赖:实例化阶段无法提前暴露半成品对象[^4^][^5^]。
-
原型作用域 Bean:每次获取新实例,无法通过缓存解决[^4^][^5^]。
-
特殊处理器干扰 :如
BeanPostProcessor
或BeanFactoryPostProcessor
参与依赖链,可能中断缓存机制[^4^][^5^]。
总的来说,Spring 的三级缓存机制通过分层存储 Bean 的不同状态(实例化 → 属性注入 → 初始化完成),结合工厂模式动态生成代理对象,巧妙解决了循环依赖问题。这一机制不仅保证了Bean生命周期的正确性,还兼容了 AOP 等高级特性,是 Spring IoC 容器的核心设计之一。
🤔 参考 14
Spring 通过 三级缓存机制 来解决 单例 Bean 的循环依赖 问题。以下是其核心原理和解决过程:
✅ 支持解决的循环依赖类型
- 仅适用于单例作用域(Singleton)的 Bean
- 只能解决通过 setter 方法注入的循环依赖
- 不能解决构造器注入的循环依赖
🔁 什么是循环依赖?
当两个或多个 Bean 相互依赖,形成闭环时,就构成了循环依赖。例如:
java
class A {
private B b;
}
class B {
private A a;
}
在创建 A 时需要注入 B,而创建 B 又需要注入 A,形成循环。
🧠 Spring 如何解决?
Spring 使用 三级缓存机制 来处理循环依赖:
-
一级缓存(singletonObjects) 存放已经完全初始化好的 Bean。
-
二级缓存(earlySingletonObjects) 存放提前暴露的"早期 Bean"(尚未完全初始化)。
-
三级缓存(singletonFactories) 存放 Bean 工厂(ObjectFactory),用于创建代理对象(如 AOP 代理)。
🔄 解决流程(以 A → B → A 为例)
-
开始创建 Bean A
- 实例化 A(调用构造函数,但未填充属性、未初始化)。
- 将 A 的工厂对象(ObjectFactory)放入三级缓存。
-
填充 A 的属性
- 发现 A 依赖 B,于是开始创建 B。
-
开始创建 Bean B
- 实例化 B,也放入三级缓存。
- 填充 B 的属性时发现依赖 A。
-
B 获取 A 的依赖
- 从 A 的三级缓存中获取 A 的早期引用(可能是代理对象)。
- 将 A 的早期引用移动到二级缓存,并从三级缓存中移除工厂。
-
B 完成初始化
- B 完成属性注入和初始化,放入一级缓存。
-
继续初始化 A
- A 从一级缓存中获取已初始化的 B。
- A 完成初始化,放入一级缓存。
🚫 为什么构造器注入无法解决?
构造器注入在实例化时就需要所有依赖对象,而此时依赖对象可能尚未创建,因此 Spring 无法提前暴露 Bean,导致循环依赖无法解决。
✅ 总结
Spring 解决循环依赖的关键在于:
- 三级缓存机制 提前暴露早期 Bean。
- ObjectFactory 支持代理对象的创建(AOP)。
- 仅适用于单例、setter 注入的场景。
Spring 的这一设计在保证灵活性的同时,有效地解决了常见的循环依赖问题。
🤔 参考 15
Spring 解决循环依赖的核心机制是通过三级缓存(Three-Level Cache)实现的,它允许在对象完全初始化之前提前暴露引用,从而打破环依赖关系。
具体实现原理如下:
-
三级缓存结构:
- 一级缓存(singletonObjects):存储完全初始化完成的单例对象
- 二级缓存(earlySingletonObjects):存储提前暴露的、未完全初始化的单例对象
- 三级缓存(singletonFactories):存储对象工厂,用于生成对象的早期引用
-
解决过程:
- 当创建对象A时,先实例化A(未设置属性),然后将A的工厂放入三级缓存
- 当A需要依赖B时,开始创建B
- B实例化后,需要依赖A,此时从三级缓存获取A的早期引用(通过工厂创建)
- 将A的早期引用放入二级缓存,同时从三级缓存移除
- B完成初始化,放入一级缓存
- A拿到B的引用,完成初始化,放入一级缓存
-
适用范围:
- 仅适用于单例Bean
- 构造器注入无法解决循环依赖(因为实例化前就需要依赖)
- 字段注入和setter注入可以通过三级缓存解决
代码层面的核心逻辑在DefaultSingletonBeanRegistry
中,关键方法包括:
getSingleton()
:从缓存获取或创建BeanaddSingletonFactory()
:添加对象工厂到三级缓存doCreateBean()
:创建Bean实例并处理依赖
这种机制既保证了Bean的完整性,又解决了循环依赖问题,是Spring容器的重要特性之一。