简介:本文系统剖析Spring框架中循环依赖的产生场景、底层解决原理及多级缓存机制,详细拆解三级缓存如何协同完成提前暴露对象引用,并给出构造器注入无法解决、setter与字段注入可解决等关键差异,帮助开发者深入理解IoC容器生命周期,避免常见配置陷阱。
在基于IoC(控制反转)容器的应用开发中,Bean之间的依赖关系由容器统一管理和注入。当两个或多个Bean之间形成了相互引用的闭环时,就产生了循环依赖。典型场景包括:
以最简单的直接循环依赖为例,假设存在两个业务类:
public class OrderService {private PaymentService paymentService;public void setPaymentService(PaymentService paymentService) {this.paymentService = paymentService;}}public class PaymentService {private OrderService orderService;public void setOrderService(OrderService orderService) {this.orderService = orderService;}}
若通过XML或注解方式声明两者互相引用,容器在启动时需要先实例化其中一个Bean,但该Bean的创建过程又要求另一个Bean已就绪,由此形成”先有鸡还是先有蛋”的困境。
Spring IoC容器通过三级缓存和提前暴露对象引用的策略来解决单例作用域下的setter/字段注入循环依赖。下面从源码层面拆解这一过程。
Spring在DefaultSingletonBeanRegistry中维护了三个Map作为缓存:
| 缓存层级 | 数据结构 | 作用 |
|---|---|---|
| 一级缓存 | singletonObjects(ConcurrentHashMap) |
存放已完全初始化的成品Bean |
| 二级缓存 | earlySingletonObjects(ConcurrentHashMap) |
存放提前暴露的早期引用(未填充属性) |
| 三级缓存 | singletonFactories(HashMap) |
存放ObjectFactory工厂,用于生成早期引用 |
这三级缓存协同工作,构成了解决循环依赖的基础设施。
以Bean A依赖Bean B、Bean B依赖Bean A为例,完整的实例化过程如下:
singletonFactories)。getObject()得到A的早期引用,放入二级缓存,并从三级缓存移除。整个过程的核心在于:通过三级缓存中的工厂对象提前暴露一个”半成品”引用,使后续依赖方能拿到一个虽未完全初始化但已存在的对象实例,从而打破死锁。
// 伪代码:提前暴露逻辑protected Object getSingleton(String beanName) {// 1. 从一级缓存获取Object bean = singletonObjects.get(beanName);if (bean == null) {// 2. 从二级缓存获取bean = earlySingletonObjects.get(beanName);if (bean == null) {// 3. 从三级缓存获取ObjectFactory factory = singletonFactories.get(beanName);if (factory != null) {bean = factory.getObject(); // 生成早期引用earlySingletonObjects.put(beanName, bean);singletonFactories.remove(beanName);}}}return bean;}
BeanCurrentlyInCreationException异常。
// 构造器注入的循环依赖——会报错public class BeanA {private final BeanB beanB;public BeanA(BeanB beanB) { // 需要BeanB已实例化this.beanB = beanB;}}
@Componentpublic class BeanA {@Lazy@Autowiredprivate BeanB beanB; // 延迟加载,不会在A实例化时立即触发B的创建}
Spring通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)与提前暴露机制,优雅地解决了单例Bean在setter/字段注入场景下的循环依赖问题。其本质是用”不完整但可用的早期引用”换取初始化流程的推进。但构造器注入和原型作用域下的循环依赖仍需开发者从设计层面主动规避。理解这一机制,不仅有助于排查启动报错,更能指导我们写出职责清晰、依赖健康的代码结构。在百度智能云等云平台的微服务实践中,合理管理Bean依赖关系同样是保障服务稳定启动的关键环节。