深度解析 Spring Boot 循环依赖:原理、源码与解决方案
在 Spring Boot 开发中,循环依赖是一个常见且容易被忽视的技术点。当两个或多个 Bean 相互引用时,就会形成循环依赖(如 A 依赖 B,B 依赖 A)。初学者往往会困惑:Spring 为什么能自动处理这种看似矛盾的依赖关系?本文将从原理、源码实现到解决方案,全方位剖析 Spring Boot 的循环依赖机制。
一、什么是循环依赖?
循环依赖指的是两个或多个 Bean 之间存在相互引用的关系,形成闭环。例如:
// ServiceA依赖ServiceB
@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}// ServiceB依赖ServiceA
@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}
上述代码中,ServiceA需要注入ServiceB,而ServiceB同时需要注入ServiceA,形成了最简单的循环依赖。在传统的对象创建过程中,这种相互依赖会导致 "先有鸡还是先有蛋" 的悖论,但 Spring 通过特殊的机制解决了大部分场景下的循环依赖问题。
二、Spring Boot 如何处理循环依赖?
Spring 容器对循环依赖的处理能力,是其 Bean 管理机制的重要体现。核心解决方案可概括为:三级缓存 + 提前暴露半成品 Bean。
1. 三级缓存的核心作用
Spring 通过三个缓存(实际上是三个 Map)来协调 Bean 的创建与依赖注入过程,这三个缓存定义在DefaultSingletonBeanRegistry中:
// 一级缓存:存储完全初始化完成的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:存储半成品Bean(已实例化但未完成属性注入和初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存:存储Bean的工厂对象,用于生成半成品Bean的代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
- 一级缓存(singletonObjects):存放已完成所有初始化步骤的 Bean,是最终可被使用的成品 Bean。
- 二级缓存(earlySingletonObjects):存放已实例化但尚未完成属性注入和初始化的半成品 Bean,用于解决循环依赖时的临时引用。
- 三级缓存(singletonFactories):存放ObjectFactory对象,该工厂用于在需要时生成 Bean 的早期引用(可能是原始对象或代理对象)。
2. 循环依赖的解决流程
以ServiceA和ServiceB的循环依赖为例,Spring 的处理流程如下:
- 创建 ServiceA:
- 调用getBean(ServiceA),检查一级缓存,未命中。
- 实例化ServiceA(通过构造方法创建对象,但未注入属性)。
- 将ServiceA的ObjectFactory放入三级缓存(singletonFactories)。
- 开始为ServiceA注入属性,发现依赖ServiceB。
- 创建 ServiceB:
- 调用getBean(ServiceB),检查一级缓存,未命中。
- 实例化ServiceB,将其ObjectFactory放入三级缓存。</