1. 问题背景
以一个电商系统为例子,Spring Boot 应用启动时抛出了循环依赖(Circular Dependency)异常,错误信息如下:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
orderController
┌─────┐
| orderServiceImpl
↑ ↓
| userServiceImpl
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
1.1 问题描述
- 循环依赖路径:
OrderController依赖OrderServiceImpl。OrderServiceImpl依赖UserServiceImpl。UserServiceImpl依赖OrderServiceImpl。- 形成闭环:
OrderServiceImpl→UserServiceImpl→OrderServiceImpl。
- Spring 的行为:
- Spring 在创建 Bean 时需要解析依赖。
- 由于循环依赖,Spring 无法完成 Bean 的创建,抛出异常。
- Spring Boot 版本影响:
- Spring Boot 2.6 之前,默认允许循环依赖(通过代理模式解决)。
- Spring Boot 2.6 及之后,默认禁止循环依赖(
spring.main.allow-circular-references默认为false)。
2. 问题分析
2.1 依赖关系分析
- OrderController:
- 注入
OrderService:@RestController public class OrderController { @Autowired private OrderService orderService; }
- 注入
- OrderServiceImpl:
- 依赖
UserService(用于获取用户信息):@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private UserService userService; @Override public OrderVO createOrder(OrderVO orderVO) { // 获取用户信息 UserEntity user = userService.findById(orderVO.getUserId()); if (user == null) { throw new RuntimeException("用户不存在"); } OrderEntity order = new OrderEntity(); BeanUtils.copyProperties(orderVO, order); order.setUserName(user.getName()); orderMapper.insert(order); return orderVO; } }
- 依赖
- UserServiceImpl:
- 依赖
OrderService(用于发送通知时查询用户最近的订单信息):@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private OrderService orderService; @Override public UserEntity findById(String userId) { UserEntity user = userMapper.selectById(userId); if (user != null) { // 查询用户最近的订单并发送通知 OrderEntity latestOrder = orderService.findLatestOrderByUserId(userId); if (latestOrder != null) { System.out.println("用户 " + user.getName() + " 的最近订单: " + latestOrder.getId()); } } return user; } // 模拟方法 public void sendNotification(String userId, String message) { System.out.println("发送通知给用户 " + userId + ": " + message); } }
- 依赖
- OrderMapper 和 UserMapper:
- 假设为 MyBatis-Plus 的 Mapper 接口:
@Mapper public interface OrderMapper extends BaseMapper<OrderEntity> { OrderEntity findLatestOrderByUserId(@Param("userId") String userId); } @Mapper public interface UserMapper extends BaseMapper<UserEntity> { }
- 假设为 MyBatis-Plus 的 Mapper 接口:
2.2 循环依赖的成因
OrderServiceImpl需要UserService来获取用户信息(userService.findById)。UserServiceImpl需要OrderService来查询用户最近的订单(orderService.findLatestOrderByUserId),以便发送通知。- 这种双向依赖形成了循环。
3. 解决方案
Spring 建议通过重构代码消除循环依赖,而不是依赖于允许循环依赖的配置。以下是几种解决方案:
3.1 方案 1:重构依赖关系(推荐)
- 目标:
- 打破
OrderServiceImpl和UserServiceImpl之间的循环依赖。
- 打破
- 方法:
- 移除
UserServiceImpl对OrderServiceImpl的依赖。 - 直接注入
OrderMapper,通过 Mapper 查询订单信息。
- 移除
- 实现:
- 修改
UserServiceImpl:@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private OrderMapper orderMapper; // 直接注入 Mapper @Override public UserEntity findById(String userId) { UserEntity user = userMapper.selectById(userId); if (user != null) { // 直接通过 Mapper 查询用户最近的订单 OrderEntity latestOrder = orderMapper.findLatestOrderByUserId(userId); if (latestOrder != null) { System.out.println("用户 " + user.getName() + " 的最近订单: " + latestOrder.getId()); } } return user; } public void sendNotification(String userId, String message) { System.out.println("发送通知给用户 " + userId + ": " + message); } }
- 修改
- 优点:
- 符合依赖倒挂原则(Service 层不直接依赖其他 Service,而是通过 Mapper 访问数据)。
- 简单直接,彻底消除循环依赖。
- 缺点:
- 如果
OrderServiceImpl中有复杂的业务逻辑(例如缓存、权限检查),直接使用 Mapper 会绕过这些逻辑。
- 如果
3.2 方案 2:使用 @Lazy 注解
- 目标:
- 通过延迟加载的方式,暂时解决循环依赖问题。
- 方法:
- 在
UserServiceImpl中,将OrderService的注入标记为@Lazy。
- 在
- 实现:
- 修改
UserServiceImpl:@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired @Lazy private OrderService orderService; @Override public UserEntity findById(String userId) { UserEntity user = userMapper.selectById(userId); if (user != null) { OrderEntity latestOrder = orderService.findLatestOrderByUserId(userId); if (latestOrder != null) { System.out.println("用户 " + user.getName() + " 的最近订单: " + latestOrder.getId()); } } return user; } public void sendNotification(String userId, String message) { System.out.println("发送通知给用户 " + userId + ": " + message); } }
- 修改
- 原理:
@Lazy注解告诉 Spring 在第一次使用orderService时再创建 Bean,而不是在UserServiceImpl初始化时立即创建。
- 优点:
- 代码改动小,适合临时解决。
- 缺点:
- 只是治标不治本,循环依赖仍然存在,可能隐藏设计问题。
3.3 方案 3:提取公共服务
- 目标:
- 将通知逻辑提取到一个独立的服务中,减少 Service 之间的直接依赖。
- 方法:
- 创建
NotificationService,专门负责通知逻辑。 UserServiceImpl依赖NotificationService,而不是OrderServiceImpl。
- 创建
- 实现:
- 创建
NotificationService:@Service public class NotificationService { @Autowired private OrderMapper orderMapper; public void sendOrderNotification(String userId, String userName) { OrderEntity latestOrder = orderMapper.findLatestOrderByUserId(userId); if (latestOrder != null) { System.out.println("用户 " + userName + " 的最近订单: " + latestOrder.getId()); } } } - 修改
UserServiceImpl:@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private NotificationService notificationService; @Override public UserEntity findById(String userId) { UserEntity user = userMapper.selectById(userId); if (user != null) { notificationService.sendOrderNotification(userId, user.getName()); } return user; } public void sendNotification(String userId, String message) { System.out.println("发送通知给用户 " + userId + ": " + message); } }
- 创建
- 优点:
- 提高了代码的可维护性和复用性,通知逻辑可以被其他服务复用。
- 彻底消除循环依赖。
- 缺点:
- 需要新增一个服务类,增加了代码量。
3.4 方案 4:允许循环依赖(不推荐)
- 目标:
- 临时解决循环依赖问题,允许 Spring 处理循环依赖。
- 方法:
- 在
application.properties或application.yml中设置:spring.main.allow-circular-references=true
- 在
- 原理:
- Spring 通过代理模式解决循环依赖。
- 优点:
- 改动最小,适合快速验证。
- 缺点:
- 掩盖了设计问题,可能导致后续维护困难。
- 不符合 Spring 的最佳实践。
4. 推荐方案
- 优先推荐:方案 1(重构依赖关系)
- 直接通过
OrderMapper查询订单信息,移除UserServiceImpl对OrderServiceImpl的依赖。 - 简单直接,符合依赖倒挂原则。
- 直接通过
- 次优选择:方案 3(提取公共服务)
- 如果通知逻辑在多个地方使用,提取
NotificationService是一个更好的设计。 - 提高了代码的可维护性和复用性。
- 如果通知逻辑在多个地方使用,提取
- 不推荐:方案 4(允许循环依赖)
- 只是临时解决方案,可能隐藏设计问题。
5. 学习总结
- 循环依赖的成因:
- Service 之间双向依赖(例如
OrderServiceImpl和UserServiceImpl互相依赖)。 - Spring Boot 2.6 及之后默认禁止循环依赖。
- Service 之间双向依赖(例如
- 解决方法:
- 重构依赖关系:通过 Mapper 直接访问数据,移除 Service 之间的依赖。
- 使用
@Lazy:延迟加载依赖,临时解决。 - 提取公共服务:将共享逻辑提取到独立服务中。
- 允许循环依赖:设置
spring.main.allow-circular-references=true(不推荐)。
- 最佳实践:
- 避免 Service 之间直接依赖,优先通过 Mapper 访问数据。
- 如果需要共享逻辑,提取公共服务或工具类。
- 编写单元测试,确保重构后功能正常。
7. 补充
- 代码设计:
- 遵循单一职责原则(SRP),避免 Service 之间耦合过高。
- 使用依赖倒挂原则(DIP),Service 层依赖 Mapper 而不是其他 Service。
- 测试:
- 编写单元测试和集成测试,确保重构后功能正常。
- 模拟循环依赖场景,验证解决方案的有效性。
- 工具:
- 使用 Spring 的依赖分析工具(例如
spring-boot-actuator)查看 Bean 依赖关系。 - 在 IDE 中使用依赖图工具(如 IntelliJ IDEA 的 Dependency Matrix)分析循环依赖。
- 使用 Spring 的依赖分析工具(例如
6927

被折叠的 条评论
为什么被折叠?



