2025-05-12
Spring
0

目录

Spring三级缓存解决循环依赖全流程深度解析
一、循环依赖的核心矛盾与Spring破局思路
二、三级缓存架构的精密设计
1. 三级缓存的协同关系
2. 三级缓存的生命周期流转
三、循环依赖解决全流程深度拆解(含AOP场景)
1. 标准场景(无AOP)流程
2. AOP增强场景流程
四、三级缓存设计的精妙之处
1. 工厂对象的核心价值
2. 同步机制的深意
五、循环依赖的边界条件分析
1. 构造器注入场景的致命缺陷
2. 原型作用域的特殊困境
3. 多层嵌套场景的处理
六、版本演进中的改进与妥协
七、开发者实践指南
1. 代码重构策略矩阵
2. 调试技巧
八、未来演进方向

Spring三级缓存解决循环依赖全流程深度解析

一、循环依赖的核心矛盾与Spring破局思路

当两个Bean形成A→B→A的依赖闭环时,传统IoC容器面临根本性矛盾:Bean的完整初始化需要其所有依赖项已就绪,而闭环中的任何Bean都无法满足该条件。Spring通过三级缓存机制实现"先暴露半成品Bean"的策略,在保证最终一致性的同时打破死锁。

二、三级缓存架构的精密设计

1. 三级缓存的协同关系

缓存层级数据结构线程安全机制主要作用
singletonObjects(一级)ConcurrentHashMap分段锁机制存储完全初始化的单例Bean
earlySingletonObjects(二级)HashMap单线程访问存储已实例化但未填充属性的Bean
singletonFactories(三级)HashMap单线程访问存储对象工厂(支持AOP动态代理)

设计哲学:一级缓存需应对并发访问,故采用ConcurrentHashMap;二三级缓存仅在Bean创建的单线程阶段使用,使用HashMap提升性能。

2. 三级缓存的生命周期流转

以A→B→A为例,展示缓存状态演变:

阶段 | A缓存位置 | B缓存位置 | 关键操作 -----|-----------|-----------|--------- A实例化 | 三级缓存(ObjectFactory) | 无 | 调用构造函数生成A原始对象 B实例化 | 三级缓存(ObjectFactory) | 三级缓存(ObjectFactory) | 检测到A依赖,触发A的缓存迁移 B属性填充 | 二级缓存(A原始对象) | 二级缓存(B原始对象) | 从三级缓存获取A并迁移至二级 A初始化完成 | 一级缓存(完整A) | 一级缓存(完整B) | 完成B注入后初始化A

三、循环依赖解决全流程深度拆解(含AOP场景)

1. 标准场景(无AOP)流程

Step 1: 创建Bean A

  • 调用无参构造函数生成A的原始对象
  • ObjectFactory放入三级缓存:
java
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

此时A处于"已实例化但未填充属性"状态

Step 2: 注入依赖B

  • populateBean()阶段发现需要注入B
  • 触发B的创建流程

Step 3: 创建Bean B

  • 实例化B的原始对象
  • 将B的工厂对象放入三级缓存
  • 在注入阶段发现需要A的依赖

Step 4: 解析A的依赖

  1. 一级缓存查找A→未命中
  2. 二级缓存查找A→未命中
  3. 三级缓存获取A的工厂对象→调用getObject()
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { return bean; // 非AOP场景直接返回原始对象 }
  1. 将A从三级缓存迁移至二级缓存

Step 5: 完成B的初始化

  • 将A的原始对象注入B
  • 执行B的属性填充和初始化方法
  • 将B放入一级缓存

Step 6: 完成A的初始化

  • 将完整的B注入A
  • 执行A的初始化方法
  • 将A升级至一级缓存

2. AOP增强场景流程

在A被代理的情况下,关键差异出现在getEarlyBeanReference()方法:

java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object proxy = ProxyFactory.getProxy(bean); // 生成CGLIB或JDK代理对象 return proxy; }

此时三级缓存返回的是代理对象,保证循环依赖链中注入的始终是代理对象。

四、三级缓存设计的精妙之处

1. 工厂对象的核心价值

三级缓存存储的ObjectFactory解决了两大难题:

  • AOP代理时机控制:延迟代理对象的生成到真正需要时
  • 重复代理防护:通过earlyProxyReferences记录避免多次代理
java
private final Set<Object> earlyProxyReferences = new HashSet<>(); protected Object getEarlyBeanReference(String beanName, Object bean) { earlyProxyReferences.add(bean); return ProxyFactory.getProxy(bean); }

2. 同步机制的深意

getSingleton()方法中采用双重检查加锁:

java
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { // 一级缓存检查 if (!containsSingleton(beanName)) { // 二级缓存检查 if (!containsEarlySingleton(beanName)) { // 三级缓存处理 addSingletonFactory(beanName, singletonFactory.getObject()); } } } }

这种设计确保在多线程场景下,同一个Bean只会被创建一次。

五、循环依赖的边界条件分析

1. 构造器注入场景的致命缺陷

当A和B通过构造器注入时:

java
@Component public class A { public A(B b) {} // 构造器注入 }

在实例化A时会直接调用new A(B),而此时B尚未创建,必然导致BeanCurrentlyInCreationException。Spring无法通过三级缓存解决此类问题。

2. 原型作用域的特殊困境

对于@Scope("prototype")的Bean:

java
@Component @Scope("prototype") public class A { @Autowired private B b; }

每次请求都会创建新实例,无法利用单例缓存机制。Spring会抛出BeanCurrentlyInCreationException

3. 多层嵌套场景的处理

对于A→B→C→A的复杂循环,处理逻辑与双层循环完全一致。Spring通过维护singletonsCurrentlyInCreation集合检测所有层级的循环依赖。

六、版本演进中的改进与妥协

在Spring Boot 2.6+版本中,默认禁用循环依赖:

yaml
spring: main: allow-circular-references: false

开启该选项后,Spring通过DependencyCircularityBreakingPostProcessor自动插入LazyResolver实现延迟注入。这种改进虽然提升了健壮性,但本质上仍建议通过重构消除设计缺陷。

七、开发者实践指南

1. 代码重构策略矩阵

问题类型重构方案实施难度风险等级
构造器循环依赖改为Setter注入★☆☆
多层嵌套循环提取公共服务类★★★
AOP代理冲突分离业务逻辑与切面★★☆
原型作用域问题改用@Scope+@Lazy★☆☆

2. 调试技巧

启用DEBUG级别日志:

java
logging.level.org.springframework.context.support.AbstractResourceBasedMessageSource=DEBUG

结合BeanCurrentlyInCreationException的堆栈跟踪,可以快速定位循环链路。

八、未来演进方向

随着Spring 6对Jakarta EE 9的全面支持,可能引入:

  1. 基于DI SPI的循环依赖检测器:在编译期或启动初期预警
  2. 自动代理解耦机制:在必要时自动生成适配器类隔离依赖
  3. 云原生优化:结合Service Mesh架构实现跨服务依赖解耦

这种设计哲学体现了Spring框架"优雅降级"的设计理念——在保持向后兼容的同时,持续优化开发者体验。理解这一机制不仅有助于解决实际问题,更能培养系统的架构思维能力。