文章目录
- 前言:循环依赖,Spring开发者的必经之路
- 一、什么是循环依赖?
-
- [1.1 循环依赖的典型场景](#1.1 循环依赖的典型场景)
- 二、为什么会出现循环依赖?
-
- [2.1 根本原因:设计层面的耦合度过高](#2.1 根本原因:设计层面的耦合度过高)
- [2.2 Spring框架的限制](#2.2 Spring框架的限制)
- [2.3 Spring的三级缓存机制](#2.3 Spring的三级缓存机制)
- 三、循环依赖的解决方案详解
-
- [3.1 方案一:重构代码,消除循环依赖(✨强烈推荐)](#3.1 方案一:重构代码,消除循环依赖(✨强烈推荐))
-
- [3.1.1 提取公共逻辑到第三方服务](#3.1.1 提取公共逻辑到第三方服务)
- [3.1.2 使用事件驱动模式](#3.1.2 使用事件驱动模式)
- [3.1.3 通过接口抽象解耦](#3.1.3 通过接口抽象解耦)
- [3.2 方案二:使用@Lazy延迟加载(⚠️谨慎使用)](#3.2 方案二:使用@Lazy延迟加载(⚠️谨慎使用))
- [3.3 方案三:改用Setter或Field注入(❌不推荐)](#3.3 方案三:改用Setter或Field注入(❌不推荐))
- [3.4 方案四:使用ApplicationContext手动获取(❌强烈不推荐)](#3.4 方案四:使用ApplicationContext手动获取(❌强烈不推荐))
- 四、循环依赖的预防与最佳实践
-
- [4.1 架构设计原则](#4.1 架构设计原则)
- [4.2 代码审查要点](#4.2 代码审查要点)
- [4.3 工具支持](#4.3 工具支持)
-
- [4.3.1 使用Spring Boot Actuator](#4.3.1 使用Spring Boot Actuator)
- [4.3.2 使用IDE插件](#4.3.2 使用IDE插件)
- [4.3.3 自定义依赖检查](#4.3.3 自定义依赖检查)
- [4.4 分层架构规范](#4.4 分层架构规范)
- 五、实战案例:电商系统循环依赖重构
-
- [5.1 问题场景](#5.1 问题场景)
- [5.2 重构方案](#5.2 重构方案)
- [5.3 重构效果](#5.3 重构效果)
- 六、总结与展望
-
- [6.1 核心要点回顾](#6.1 核心要点回顾)
- [6.2 未来发展趋势](#6.2 未来发展趋势)
- [6.3 给开发者的建议](#6.3 给开发者的建议)
前言:循环依赖,Spring开发者的必经之路
在日常的SpringBoot开发中,你是否遇到过这样的错误信息?
java
Error creating bean with name 'userService':
Requested bean is currently in creation: Is there an unresolvable circular reference?
这正是Spring的循环依赖异常。它不仅困扰着新手开发者,即便是经验丰富的架构师也常常需要花费大量时间来解决这个问题。本文将深入剖析循环依赖的本质原因,提供多种解决方案,并分享最佳实践,帮助你在开发中彻底掌握这一关键技术点。
一、什么是循环依赖?
循环依赖(Circular Dependency) 指的是两个或多个Bean相互依赖,形成一个"死循环"的依赖关系。简单来说,就是A依赖B,B又依赖A,或者更复杂的A→B→C→A的环形依赖。
1.1 循环依赖的典型场景
java
// 场景1:直接循环依赖
@Service
public class UserService {
private final OrderService orderService; // User依赖Order
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService; // Order又依赖User
public OrderService(UserService userService) {
this.userService = userService;
}
}
// 场景2:间接循环依赖
@Service
public class ServiceA {
private final ServiceB serviceB; // A依赖B
}
@Service
public class ServiceB {
private final ServiceC serviceC; // B依赖C
}
@Service
public class ServiceC {
private final ServiceA serviceA; // C依赖A,形成环
}
二、为什么会出现循环依赖?
2.1 根本原因:设计层面的耦合度过高
循环依赖的本质是架构设计问题,而非Spring框架的缺陷。当两个本应职责分离的服务相互持有对方的引用时,就违反了软件设计的核心原则:
- 违反单一职责原则(SRP):每个类应该只有一个引起变化的原因
- 违反依赖倒置原则(DIP):高层模块不应依赖低层模块,二者都应依赖抽象
- 违反迪米特法则(LoD):一个对象应该对其他对象保持最少的了解
2.2 Spring框架的限制
Spring对循环依赖的支持是有限制的:
循环依赖场景
注入方式
构造器注入
Setter/Field注入
Bean作用域
Bean作用域
单例Singleton
原型Prototype
单例Singleton
原型Prototype
❌ 不支持
❌ 不支持
✅ 支持
❌ 不支持
关键点总结:
- 仅支持单例Bean 的Setter/Field注入
- 不支持构造器注入的循环依赖
- 不支持原型(Prototype)Bean的循环依赖
2.3 Spring的三级缓存机制
要理解为什么某些情况支持循环依赖,需要了解Spring的三级缓存机制:
java
// Spring 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工厂,用于创建早期Bean引用
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
解决流程:
- A开始创建 → 放入singletonFactories(三级缓存)
- A发现依赖B → 开始创建B
- B开始创建 → 放入singletonFactories
- B发现依赖A → 从三级缓存获取A的早期引用
- B创建完成 → 放入singletonObjects(一级缓存)
- A注入B的完整实例 → A创建完成
三、循环依赖的解决方案详解
3.1 方案一:重构代码,消除循环依赖(✨强烈推荐)
这是最根本、最优雅的解决方案,也是提升代码质量的绝佳机会。
3.1.1 提取公共逻辑到第三方服务
适用场景:两个服务有共同的业务逻辑
java
// 1. 创建独立的验证服务
@Component
@Slf4j
public class ValidationService {
/**
* 用户验证逻辑
* @param userId 用户ID
* @return 验证结果
*/
public ValidationResult validateUser(Long userId) {
// 独立的验证逻辑,不依赖任何业务服务
log.debug("Validating user with ID: {}", userId);
// 实际验证逻辑(这里简化处理)
boolean isValid = userId != null && userId > 0;
String message = isValid ? "用户验证通过" : "用户ID无效";
return new ValidationResult(isValid, message);
}
/**
* 订单验证逻辑
* @param order 订单对象
* @return 验证结果
*/
public ValidationResult validateOrder(Order order) {
log.debug("Validating order: {}", order);
// 实际的订单验证逻辑
boolean isValid = order != null
&& order.getId() != null
&& order.getAmount() != null
&& order.getAmount().compareTo(BigDecimal.ZERO) > 0;
String message = isValid ? "订单验证通过" : "订单信息不完整";
return new ValidationResult(isValid, message);
}
// 验证结果封装类
@Data
@AllArgsConstructor
public static class ValidationResult {
private boolean valid;
private String message;
}
}
// 2. 重构UserService,移除对OrderService的直接依赖
@Service
@Slf4j
public class UserService {
private final ValidationService validationService;
private final UserRepository userRepository;
public UserService(ValidationService validationService,
UserRepository userRepository) {
this.validationService = validationService;
this.userRepository = userRepository;
}
public User createUser(UserDTO userDTO) {
log.info("开始创建用户: {}", userDTO.getUsername());
// 使用验证服务
ValidationService.ValidationResult result =
validationService.validateUser(userDTO.getId());
if (!result.isValid()) {
throw new BusinessException("用户验证失败: " + result.getMessage());
}
// 用户创建逻辑
User user = convertToEntity(userDTO);
return userRepository.save(user);
}
// 其他业务方法...
}
// 3. 重构OrderService,同样移除对UserService的直接依赖
@Service
@Slf4j
public class OrderService {
private final ValidationService validationService;
private final OrderRepository orderRepository;
public OrderService(ValidationService validationService,
OrderRepository orderRepository) {
this.validationService = validationService;
this.orderRepository = orderRepository;
}
public Order createOrder(OrderDTO orderDTO) {
log.info("开始创建订单,用户ID: {}", orderDTO.getUserId());
// 验证订单
ValidationService.ValidationResult orderValidation =
validationService.validateOrder(convertToEntity(orderDTO));
if (!orderValidation.isValid()) {
throw new BusinessException("订单验证失败: " + orderValidation.getMessage());
}
// 订单创建逻辑
Order order = convertToEntity(orderDTO);
return orderRepository.save(order);
}
}
优点:
- ✅ 彻底解决循环依赖
- ✅ 职责单一,符合SRP原则
- ✅ 代码复用性高
- ✅ 易于单元测试
- ✅ 提升代码可维护性
3.1.2 使用事件驱动模式
适用场景:服务间需要通信但不需要直接引用
java
// 1. 定义领域事件
@Data
@AllArgsConstructor
public class UserCreatedEvent {
private Long userId;
private String username;
private LocalDateTime createTime;
}
@Data
@AllArgsConstructor
public class OrderCreatedEvent {
private Long orderId;
private Long userId;
private BigDecimal amount;
}
// 2. 使用ApplicationEventPublisher发布事件
@Service
@Slf4j
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
public UserService(UserRepository userRepository,
ApplicationEventPublisher eventPublisher) {
this.userRepository = userRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
public User createUser(UserDTO userDTO) {
// 创建用户
User user = userRepository.save(convertToEntity(userDTO));
// 发布用户创建事件
UserCreatedEvent event = new UserCreatedEvent(
user.getId(),
user.getUsername(),
LocalDateTime.now()
);
log.info("发布用户创建事件: {}", event);
eventPublisher.publishEvent(event);
return user;
}
}
// 3. OrderService监听事件
@Service
@Slf4j
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
// 监听用户创建事件
@EventListener
@Transactional
public void handleUserCreatedEvent(UserCreatedEvent event) {
log.info("收到用户创建事件,为用户 {} 创建欢迎订单", event.getUsername());
// 为新用户创建一个欢迎订单
Order welcomeOrder = Order.builder()
.userId(event.getUserId())
.orderNo("WELCOME_" + System.currentTimeMillis())
.amount(new BigDecimal("0.01"))
.status(OrderStatus.CREATED)
.build();
orderRepository.save(welcomeOrder);
// 发布订单创建事件
// eventPublisher.publishEvent(new OrderCreatedEvent(...));
}
// 其他业务方法
public Order createOrder(OrderDTO orderDTO) {
// 直接创建订单的逻辑
return orderRepository.save(convertToEntity(orderDTO));
}
}
优点:
- ✅ 完全解耦,服务间无直接依赖
- ✅ 支持异步处理
- ✅ 系统扩展性好
- ✅ 符合领域驱动设计(DDD)
3.1.3 通过接口抽象解耦
适用场景:需要依赖但希望降低耦合度
java
// 1. 定义抽象接口
public interface UserValidator {
ValidationResult validateUser(Long userId);
}
public interface OrderProcessor {
ProcessResult processOrder(Order order);
}
// 2. 实现接口
@Component
public class UserValidatorImpl implements UserValidator {
private final UserRepository userRepository;
public UserValidatorImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public ValidationResult validateUser(Long userId) {
// 实现验证逻辑
return userRepository.existsById(userId)
? ValidationResult.success()
: ValidationResult.fail("用户不存在");
}
}
@Component
public class OrderProcessorImpl implements OrderProcessor {
@Override
public ProcessResult processOrder(Order order) {
// 实现订单处理逻辑
return ProcessResult.success("订单处理完成");
}
}
// 3. 通过接口依赖
@Service
public class UserService {
private final UserValidator userValidator;
public UserService(UserValidator userValidator) {
this.userValidator = userValidator;
// 不再直接依赖OrderService
}
}
@Service
public class OrderService {
private final OrderProcessor orderProcessor;
private final UserValidator userValidator;
public OrderService(OrderProcessor orderProcessor,
UserValidator userValidator) {
this.orderProcessor = orderProcessor;
this.userValidator = userValidator;
// 不再直接依赖UserService
}
}
3.2 方案二:使用@Lazy延迟加载(⚠️谨慎使用)
适用场景:快速修复,临时解决方案
java
@Service
@Slf4j
public class UserService {
private final OrderService orderService;
// 使用@Lazy延迟加载OrderService
public UserService(@Lazy OrderService orderService) {
this.orderService = orderService;
log.info("UserService构造器被调用,OrderService被延迟注入");
}
public void processUserOrder(Long userId) {
// 只有实际调用时才会初始化OrderService
List<Order> orders = orderService.findOrdersByUserId(userId);
// 处理逻辑...
}
}
@Service
@Slf4j
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
log.info("OrderService构造器被调用");
}
}
工作原理:
- Spring创建UserService时,不立即创建OrderService
- 而是创建一个OrderService的代理对象
- 当UserService真正调用orderService的方法时,才初始化真实的OrderService
- 此时OrderService可以正常注入已存在的UserService
缺点:
- ❌ 只是绕过问题,没有真正解决设计缺陷
- ❌ 可能掩盖了更严重的架构问题
- ❌ 调试困难,异常栈不直观
- ❌ 可能影响启动性能(第一次调用时有延迟)
3.3 方案三:改用Setter或Field注入(❌不推荐)
适用场景:历史遗留代码的临时修复
java
// 不推荐的方式:字段注入
@Service
public class UserService {
@Autowired // 字段注入
private OrderService orderService;
// 构造器中没有OrderService
public UserService() {
// 空构造器
}
}
@Service
public class OrderService {
@Autowired // 字段注入
private UserService userService;
public OrderService() {
// 空构造器
}
}
为什么Spring能处理这种情况?
因为字段注入发生在Bean构造完成之后,此时:
- UserService实例已创建(但orderService字段为null)
- OrderService实例已创建(但userService字段为null)
- Spring通过反射为两个字段注入值
严重缺点:
- 破坏不可变性:字段可以被重新赋值
- 测试困难:必须使用反射或Spring容器来设置依赖
- 隐藏依赖:从构造器看不出所有依赖
- 违背最佳实践:Spring官方推荐构造器注入
- 可能NullPointerException:如果Spring配置有问题,运行时才暴露
3.4 方案四:使用ApplicationContext手动获取(❌强烈不推荐)
适用场景:极端情况,其他方案都不可行时
java
@Component
@Slf4j
public class ServiceLocator implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ServiceLocator.context = applicationContext;
log.info("ApplicationContext已设置");
}
/**
* 获取Bean实例
*/
public static <T> T getBean(Class<T> clazz) {
if (context == null) {
throw new IllegalStateException("ApplicationContext未初始化");
}
return context.getBean(clazz);
}
/**
* 获取Bean实例(按名称)
*/
public static Object getBean(String name) {
if (context == null) {
throw new IllegalStateException("ApplicationContext未初始化");
}
return context.getBean(name);
}
}
// 使用方式
@Service
public class UserService {
public void process() {
// 手动获取OrderService
OrderService orderService = ServiceLocator.getBean(OrderService.class);
orderService.doSomething();
}
}
为什么强烈不推荐?
- 破坏控制反转(IoC):主动获取依赖,而不是被动接收
- 依赖隐藏:类的依赖关系不明显
- 难以测试:必须启动Spring容器才能测试
- 类型不安全:getBean(String)可能返回类型错误的对象
- 违背Spring设计哲学
四、循环依赖的预防与最佳实践
4.1 架构设计原则
良好的架构设计
单一职责原则
接口隔离原则
依赖倒置原则
每个类只有一个职责
客户端不应依赖不需要的接口
依赖抽象而非实现
避免循环依赖
4.2 代码审查要点
- 检查构造器参数:构造器参数中是否有其他Service
- 分析依赖图:使用工具生成Bean依赖关系图
- 分层架构:严格遵循Controller → Service → Repository分层
- 依赖方向:确保依赖是单向的,不要形成环
4.3 工具支持
4.3.1 使用Spring Boot Actuator
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: beans
endpoint:
beans:
enabled: true
访问 http://localhost:8080/actuator/beans 查看所有Bean及其依赖。
4.3.2 使用IDE插件
- IntelliJ IDEA: Spring Bean Dependency Diagram
- Eclipse: Spring Tools Suite
- VS Code: Spring Boot Extension Pack
4.3.3 自定义依赖检查
java
@Component
public class CircularDependencyDetector implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition beanDefinition =
((ConfigurableApplicationContext) context)
.getBeanFactory().getBeanDefinition(beanName);
if (beanDefinition instanceof AbstractBeanDefinition) {
AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDefinition;
String[] dependsOn = abd.getDependsOn();
if (dependsOn != null) {
// 检查循环依赖
checkCircularDependencies(beanName, dependsOn, context);
}
}
}
}
private void checkCircularDependencies(String beanName,
String[] dependencies,
ApplicationContext context) {
// 实现循环依赖检查逻辑
// 可以使用图算法检测环
}
}
4.4 分层架构规范
java
// 正确的分层依赖方向
┌─────────────────┐
│ Controller │ ← 只依赖Service
└─────────────────┘
↓
┌─────────────────┐
│ Service │ ← 可以依赖其他Service,但要避免循环
└─────────────────┘
↓
┌─────────────────┐
│ Repository │ ← 只依赖Entity和数据源
└─────────────────┘
↓
┌─────────────────┐
│ Database │
└─────────────────┘
// 反例:错误的依赖方向
┌─────────────────┐
│ Service A │
└─────────────────┘
↓ ↓
┌─────────┐ ┌─────────┐
│Service B│←│Service C│
└─────────┘ └─────────┘
↓ ↓
┌─────────────────┐
│ Controller │
└─────────────────┘
五、实战案例:电商系统循环依赖重构
5.1 问题场景
假设电商系统中有以下循环依赖:
CartService依赖OrderService(创建订单时需要购物车信息)OrderService依赖InventoryService(创建订单需要检查库存)InventoryService依赖CartService(库存回补时需要更新购物车推荐)
5.2 重构方案
java
// 1. 创建领域事件
@Data
@AllArgsConstructor
public class CartCheckedOutEvent {
private Long cartId;
private Long userId;
private List<CartItem> items;
private LocalDateTime checkoutTime;
}
@Data
@AllArgsConstructor
public class InventoryUpdatedEvent {
private Long productId;
private Integer quantity;
private InventoryUpdateType updateType;
}
// 2. 重构CartService
@Service
@Slf4j
public class CartService {
private final CartRepository cartRepository;
private final ApplicationEventPublisher eventPublisher;
public CartService(CartRepository cartRepository,
ApplicationEventPublisher eventPublisher) {
this.cartRepository = cartRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
public Order checkoutCart(Long cartId) {
Cart cart = cartRepository.findById(cartId)
.orElseThrow(() -> new NotFoundException("购物车不存在"));
// 发布购物车结算事件
CartCheckedOutEvent event = new CartCheckedOutEvent(
cartId,
cart.getUserId(),
cart.getItems(),
LocalDateTime.now()
);
eventPublisher.publishEvent(event);
// 清空购物车
cart.clearItems();
cartRepository.save(cart);
return null; // Order由事件监听器创建
}
}
// 3. 重构OrderService
@Service
@Slf4j
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@EventListener
@Transactional
public void handleCartCheckedOut(CartCheckedOutEvent event) {
log.info("收到购物车结算事件,为用户 {} 创建订单", event.getUserId());
// 创建订单逻辑
Order order = createOrderFromCart(event);
orderRepository.save(order);
// 可以继续发布OrderCreatedEvent
}
private Order createOrderFromCart(CartCheckedOutEvent event) {
// 转换逻辑
return new Order();
}
}
// 4. 重构InventoryService
@Service
@Slf4j
public class InventoryService {
private final InventoryRepository inventoryRepository;
private final ApplicationEventPublisher eventPublisher;
public InventoryService(InventoryRepository inventoryRepository,
ApplicationEventPublisher eventPublisher) {
this.inventoryRepository = inventoryRepository;
this.eventPublisher = eventPublisher;
}
@EventListener
@Transactional
public void handleOrderCreated(OrderCreatedEvent event) {
// 扣减库存逻辑
updateInventory(event.getItems(), InventoryUpdateType.DECREASE);
}
public void replenishInventory(Long productId, Integer quantity) {
// 库存回补逻辑
updateInventory(productId, quantity, InventoryUpdateType.INCREASE);
// 发布库存更新事件
InventoryUpdatedEvent event = new InventoryUpdatedEvent(
productId, quantity, InventoryUpdateType.INCREASE
);
eventPublisher.publishEvent(event);
}
}
// 5. CartRecommendationService(新增,专门处理推荐逻辑)
@Service
@Slf4j
public class CartRecommendationService {
@EventListener
public void handleInventoryUpdated(InventoryUpdatedEvent event) {
if (event.getUpdateType() == InventoryUpdateType.INCREASE) {
// 库存回补时,更新购物车推荐
updateRecommendations(event.getProductId());
}
}
private void updateRecommendations(Long productId) {
// 推荐逻辑
log.info("基于产品 {} 的库存回补更新推荐", productId);
}
}
5.3 重构效果
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 循环依赖 | 存在 | 完全消除 |
| 代码耦合度 | 高 | 低 |
| 可测试性 | 差 | 优秀 |
| 扩展性 | 差 | 优秀 |
| 代码清晰度 | 混乱 | 清晰 |
六、总结与展望
6.1 核心要点回顾
- 循环依赖的根本原因是设计问题,不是Spring的bug
- Spring有限支持循环依赖:仅单例Bean的Setter/Field注入
- 最佳解决方案是重构:提取公共逻辑、使用事件驱动、接口抽象
- @Lazy是临时方案,不要作为长期解决方案
- 避免使用字段注入和ServiceLocator模式
6.2 未来发展趋势
随着微服务和云原生架构的普及,循环依赖问题的解决方式也在演进:
- 服务网格(Service Mesh):通过Sidecar代理处理服务间通信
- 领域驱动设计(DDD):通过限界上下文彻底隔离领域
- CQRS模式:命令和查询分离,避免读写模型互相依赖
- 事件溯源(Event Sourcing):通过事件流驱动系统状态变化
6.3 给开发者的建议
- 预防优于治疗:在架构设计阶段就避免循环依赖
- 定期代码审查:使用工具检查依赖关系
- 持续重构:不要积累技术债务
- 学习设计模式:掌握适配器、观察者、中介者等模式
- 理解业务领域:好的技术架构源于对业务的深刻理解
记住:循环依赖不是需要绕过的障碍,而是改进代码设计的契机。每一次解决循环依赖的过程,都是提升架构设计能力的机会。
如需获取更多关于SpringBoot自动配置原理、内嵌Web容器、Starter开发指南、生产级特性(监控、健康检查、外部化配置)等内容,请持续关注本专栏《SpringBoot核心技术深度剖析》系列文章。