Spring循环依赖解决方案
约 1561 字大约 5 分钟
springcircular-dependency
2025-03-25
概述
循环依赖是指两个或多个 Bean 之间形成相互依赖的闭环。例如 A 依赖 B,B 又依赖 A。Spring 通过三级缓存机制解决了 Singleton 作用域下 Setter/字段注入的循环依赖问题,但构造器注入和 Prototype 作用域的循环依赖无法自动解决。
循环依赖的类型
// 直接循环依赖示例
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}三级缓存机制
Spring 使用三级缓存(三个 Map)来解决循环依赖:
// DefaultSingletonBeanRegistry 中定义的三级缓存
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放提前暴露的Bean实例(尚未完成属性填充和初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放Bean的ObjectFactory(用于生成早期引用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}三级缓存详解
为什么需要三级缓存?
| 缓存级别 | 存储内容 | 作用 |
|---|---|---|
一级缓存 singletonObjects | 完整的 Bean 实例 | 提供最终可用的 Bean |
二级缓存 earlySingletonObjects | 早期的 Bean 引用 | 避免重复创建代理对象 |
三级缓存 singletonFactories | ObjectFactory 工厂 | 延迟创建代理,支持 AOP |
ObjectFactory 的作用
三级缓存中存储的是 ObjectFactory 而非直接的 Bean 引用,目的是支持 AOP 场景下的代理创建:
// Spring 源码简化
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessors()) {
// 如果有AOP代理,在这里提前创建代理对象
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
return exposedObject;
}
// 添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));getSingleton 源码解析
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) {
synchronized (this.singletonObjects) {
// Double-check
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 3. 查三级缓存
ObjectFactory<?> singletonFactory =
this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用 ObjectFactory 获取早期引用
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 移除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}构造器注入为何无法解决循环依赖
构造器注入时,Bean 的实例化本身就需要依赖对象,此时 Bean 尚未创建,无法放入任何缓存。三级缓存是在构造器调用之后才能发挥作用的。
// 构造器循环依赖 - 会报错
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { // 需要B才能创建A
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) { // 需要A才能创建B
this.serviceA = serviceA;
}
}解决方案:使用 @Lazy
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
// 注入的是ServiceB的代理对象,实际使用时才真正获取Bean
this.serviceB = serviceB;
}
}Prototype 作用域的循环依赖
Prototype 作用域的 Bean 不使用缓存,每次获取都创建新实例,因此无法通过三级缓存解决循环依赖:
@Component
@Scope("prototype")
public class PrototypeA {
@Autowired
private PrototypeB prototypeB; // 每次都是新实例,无法缓存
}
@Component
@Scope("prototype")
public class PrototypeB {
@Autowired
private PrototypeA prototypeA; // 无限循环创建
}
// 结果:抛出 BeanCurrentlyInCreationException解决循环依赖的最佳实践
1. 重构设计,消除循环依赖
// 重构前:A <-> B 循环依赖
// 重构后:提取公共逻辑到 C
@Service
public class ServiceA {
@Autowired
private CommonService commonService;
}
@Service
public class ServiceB {
@Autowired
private CommonService commonService;
}
@Service
public class CommonService {
// A 和 B 的公共依赖
}2. 使用事件驱动解耦
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// 通过事件解耦,不直接依赖 InventoryService
eventPublisher.publishEvent(new OrderCreatedEvent(order));
}
}
@Service
public class InventoryService {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
reduceStock(event.getOrder());
}
}3. 使用 ObjectProvider 延迟获取
@Service
public class ServiceA {
private final ObjectProvider<ServiceB> serviceBProvider;
public ServiceA(ObjectProvider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
public void doWork() {
ServiceB serviceB = serviceBProvider.getObject();
serviceB.process();
}
}Spring Boot 2.6+ 的变化
从 Spring Boot 2.6 开始,默认禁止循环依赖。如果存在循环依赖,启动时会直接报错:
The dependencies of some of the beans in the application context form a cycle:
serviceA -> serviceB -> serviceA可以通过配置允许循环依赖(不推荐):
spring.main.allow-circular-references=true推荐做法: 修改代码消除循环依赖,而不是开启此配置。
总结
Spring 通过三级缓存机制巧妙地解决了 Singleton 作用域下 Setter/字段注入的循环依赖问题。三级缓存中 ObjectFactory 的设计是为了在需要时才创建 AOP 代理,保持代理创建时机的一致性。但构造器注入和 Prototype 作用域的循环依赖无法自动解决。最佳实践是从架构设计层面避免循环依赖,使用事件驱动、接口抽象等方式进行解耦。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于