一、循环依赖的本质与危害
循环依赖是Spring容器初始化过程中最常见的陷阱之一,其本质是两个或多个Bean在实例化阶段形成相互引用的闭环。以典型的业务场景为例:
@Servicepublic class OrderService {@Autowiredprivate PaymentService paymentService;}@Servicepublic class PaymentService {@Autowiredprivate OrderService orderService;}
当Spring尝试初始化OrderService时,发现其依赖PaymentService,转而初始化PaymentService时又发现需要OrderService,此时若没有特殊处理机制,整个初始化流程将陷入无限递归的死循环。
这种依赖关系在复杂业务系统中尤为常见,例如:
- 订单服务依赖支付服务,支付服务又依赖风控服务,风控服务再依赖订单服务
- 缓存服务依赖监控服务,监控服务又依赖缓存服务
若未妥善处理循环依赖,将导致:
- 容器启动失败,抛出
BeanCurrentlyInCreationException - 系统性能下降(反复尝试初始化)
- 难以定位的复杂错误日志
二、三级缓存架构深度解析
Spring通过三级缓存的协作机制,在保证线程安全的前提下实现了循环依赖的优雅解决。其核心数据结构定义在DefaultSingletonBeanRegistry类中:
// 一级缓存:存储完全初始化完成的Beanprivate 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);
缓存层级协作原理:
- 一级缓存:最终成品库,存放通过完整初始化流程(属性填充、Aware接口回调、初始化方法等)的Bean
- 二级缓存:临时缓冲区,存放已完成实例化但尚未完成属性填充的Bean
- 三级缓存:代理工厂库,存放
ObjectFactory接口实现,用于在需要时生成代理对象
三、完整解决流程详解
以OrderService和PaymentService的循环依赖为例,解析Spring的解决步骤:
1. 初始化OrderService
- 实例化阶段:通过反射创建
OrderService的原始对象(此时paymentService字段为null) - 三级缓存注册:将
OrderService的ObjectFactory存入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
- 属性填充阶段:发现依赖
PaymentService,触发PaymentService的初始化
2. 初始化PaymentService
- 实例化阶段:创建
PaymentService原始对象 - 三级缓存注册:将
PaymentService的ObjectFactory存入三级缓存 - 属性填充阶段:发现依赖
OrderService,此时执行以下操作:- 从一级缓存查找
OrderService→ 不存在 - 从二级缓存查找
OrderService→ 不存在 - 从三级缓存获取
OrderService的ObjectFactory - 调用
getObject()方法获取早期引用(可能生成代理对象) - 将早期引用存入二级缓存
- 从三级缓存移除
OrderService的ObjectFactory
- 从一级缓存查找
3. 完成PaymentService初始化
- 完成
PaymentService的属性填充 - 执行初始化方法(如
@PostConstruct) - 将完全初始化的
PaymentService存入一级缓存 - 从二级缓存移除
PaymentService的早期引用
4. 完成OrderService初始化
- 将获取到的
PaymentService(已完全初始化)注入OrderService - 完成
OrderService的初始化流程 - 将完全初始化的
OrderService存入一级缓存
四、关键设计思想解析
1. 延迟代理生成策略
三级缓存通过ObjectFactory延迟代理对象的创建,解决了AOP代理与循环依赖的冲突问题。当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) {exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp).getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}
这种设计确保代理对象只在真正需要时创建,避免了不必要的代理开销。
2. 线程安全保障机制
通过ConcurrentHashMap实现的一级/二级缓存,配合同步控制的singletonFactories,保证了多线程环境下的数据一致性。关键同步点出现在:
// 获取单例的同步控制public Object getSingleton(String beanName, boolean allowEarlyReference) {synchronized (this.singletonObjects) {// 缓存查找逻辑...}}
3. 缓存清理策略
Spring在Bean初始化完成后会及时清理各级缓存:
- 成功初始化后:从二级/三级缓存移除对应条目
- 初始化失败时:清空所有相关缓存
- 容器关闭时:完全清空三级缓存
五、开发实践建议
- 优先使用构造器注入:虽然Spring能解决Setter注入的循环依赖,但构造器注入能更早暴露设计问题
- 合理拆分服务:当出现复杂循环依赖时,考虑是否需要拆分服务或引入中间层
- 避免原型Bean循环依赖:三级缓存机制仅适用于单例Bean,原型Bean的循环依赖需要重构设计
- 监控缓存状态:在复杂系统中,可通过
DefaultSingletonBeanRegistry的getSingletonMutex()等方法监控缓存状态
六、常见问题解答
Q1:为什么需要三级缓存而不是两级?
A:三级缓存的核心价值在于解决AOP代理对象的创建时机问题。如果只有两级缓存,当Bean需要被代理时,要么提前创建代理对象(违反延迟初始化原则),要么无法处理循环依赖。
Q2:所有循环依赖都能被解决吗?
A:以下情况无法解决:
- 构造器注入形成的循环依赖
- 原型Bean的循环依赖
- 多实例Bean之间的循环依赖
Q3:三级缓存会影响性能吗?
A:合理设计的三级缓存机制对性能影响极小。二级缓存使用ConcurrentHashMap保证高并发读取性能,三级缓存的ObjectFactory只在必要时调用,且每个Bean只会被处理一次。
通过这种精妙的设计,Spring框架在保持高灵活性的同时,成功解决了循环依赖这一复杂问题。理解其实现原理不仅有助于解决面试问题,更能指导我们设计出更健壮的企业级应用。