在复杂的 Java 系统开发中,随着业务规模扩大和模块增多,服务间的依赖关系往往变得错综复杂。其中,循环依赖是最令人头疼的问题之一,它不仅会导致系统启动失败,还会降低代码可维护性,成为技术债务的潜在来源。本文将通过一个真实案例,深入剖析循环依赖的产生根源,详细讲解如何通过重构拆分中间服务来彻底解决这一问题,并提供可直接落地的代码方案和最佳实践。
循环依赖的本质与危害
什么是循环依赖
循环依赖指的是两个或多个组件之间相互依赖,形成一个封闭的依赖环。在 Java Spring 框架中,最常见的是构造器循环依赖 和字段循环依赖两种形式。

如上图所示,订单服务依赖库存服务进行库存扣减,而库存服务又依赖订单服务查询相关订单信息,形成了典型的双向循环依赖。
循环依赖的危害
-
系统启动失败 :Spring 容器在初始化具有循环依赖的 Bean 时,若未采用合适的注入方式(如构造器注入),会直接抛出
BeanCurrentlyInCreationException
异常。 -
代码可读性下降:循环依赖会使代码结构变得混乱,新接手的开发者难以理清模块间的调用关系。
-
维护成本增加:修改一个服务可能会波及多个依赖它的服务,形成 "牵一发而动全身" 的局面。
-
测试困难:循环依赖的组件难以进行单元测试,必须同时初始化所有相关依赖才能完成测试。
-
扩展性受限:存在循环依赖的模块往往耦合紧密,难以单独进行扩展或替换。
Spring 对循环依赖的处理机制
Spring 容器对循环依赖的处理能力有限,仅支持单例 Bean 的字段注入 或setter 注入的循环依赖,这是通过三级缓存实现的:
- 一级缓存:存储完全初始化好的 Bean
- 二级缓存:存储早期暴露的 Bean 实例(未完全初始化)
- 三级缓存:存储 Bean 工厂对象,用于生成 Bean 的早期实例

但对于构造器注入的循环依赖,Spring 无法解决,会直接抛出异常。这也是阿里巴巴 Java 开发手册强制要求 "避免构造器注入循环依赖" 的原因。
案例分析:电商系统中的循环依赖困境
业务背景
我们以一个典型的电商系统为例,该系统包含订单模块和库存模块:
- 订单模块负责订单创建、支付、取消等功能
- 库存模块负责库存查询、扣减、恢复等功能
在业务流程中:
- 创建订单时需要扣减库存(订单依赖库存)
- 库存扣减后需要记录订单关联信息(库存依赖订单)
随着业务发展,两个模块间的依赖逐渐加深,最终形成了难以解开的循环依赖。
循环依赖代码实现
实体类设计
首先定义核心实体类:
package com.ecommerce.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
*
* @author ken
*/
@Data
@TableName("t_order")
@Schema(description = "订单信息")
public class Order {
/**
* 订单ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "订单ID")
private Long id;
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 商品ID
*/
@Schema(description = "商品ID")
private Long productId;
/**
* 订单金额
*/
@Schema(description = "订单金额")
private BigDecimal amount;
/**
* 订单状态:0-待支付 1-已支付 2-已取消 3-已完成
*/
@Schema(description = "订单状态:0-待支付 1-已支付 2-已取消 3-已完成")
private Integer status;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
package com.ecommerce.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 库存实体类
*
* @author ken
*/
@Data
@TableName("t_inventory")
@Schema(description = "库存信息")
public class Inventory {
/**
* 库存ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "库存ID")
private Long id;
/**
* 商品ID
*/
@Schema(description = "商品ID")
private Long productId;
/**
* 库存数量
*/
@Schema(description = "库存数量")
private Integer quantity;
/**
* 锁定数量
*/
@Schema(description = "锁定数量")
private Integer lockedQuantity;
/**
* 最后操作订单ID
*/
@Schema(description = "最后操作订单ID")
private Long lastOrderId;
/**
* 更新时间
*/
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
Mapper 接口
package com.ecommerce.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ecommerce.entity.Order;
import org.apache.ibatis.annotations.Mapper;
/**
* 订单Mapper接口
*
* @author ken
*/
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
package com.ecommerce.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ecommerce.entity.Inventory;
import org.apache.ibatis.annotations.Mapper;
/**
* 库存Mapper接口
*
* @author ken
*/
@Mapper
public interface InventoryMapper extends BaseMapper<Inventory> {
}
服务接口与实现
package com.ecommerce.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ecommerce.entity.Order;
import com.ecommerce.vo.OrderCreateVO;
/**
* 订单服务接口
*
* @author ken
*/
public interface OrderService extends IService<Order> {
/**
* 创建订单
*
* @param orderCreateVO 订单创建参数
* @return 订单ID
*/
Long createOrder(OrderCreateVO orderCreateVO);
/**
* 根据订单ID查询订单
*
* @param orderId 订单ID
* @return 订单信息
*/
Order getOrderById(Long orderId);
/**
* 取消订单
*
* @param orderId 订单ID
* @return 是否取消成功
*/
boolean cancelOrder(Long orderId);
}
package com.ecommerce.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ecommerce.entity.Inventory;
/**
* 库存服务接口
*
* @author ken
*/
public interface InventoryService extends IService<Inventory> {
/**
* 扣减库存
*
* @param productId 商品ID
* @param quantity 数量
* @param orderId 订单ID
* @return 是否扣减成功
*/
boolean deductInventory(Long productId, Integer quantity, Long orderId);
/**
* 恢复库存
*
* @param productId 商品ID
* @param quantity 数量
* @param orderId 订单ID
* @return 是否恢复成功
*/
boolean restoreInventory(Long productId, Integer quantity, Long orderId);
/**
* 查询商品库存
*
* @param productId 商品ID
* @return 库存信息
*/
Inventory getInventoryByProductId(Long productId);
}
循环依赖的服务实现
package com.ecommerce.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ecommerce.entity.Order;
import com.ecommerce.entity.Inventory;
import com.ecommerce.mapper.OrderMapper;
import com.ecommerce.service.OrderService;
import com.ecommerce.service.InventoryService;
import com.ecommerce.vo.OrderCreateVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单服务实现类(存在循环依赖)
*
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
/**
* 依赖库存服务 - 形成循环依赖的关键点
*/
private final InventoryService inventoryService;
/**
* 创建订单
*
* @param orderCreateVO 订单创建参数
* @return 订单ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long createOrder(OrderCreateVO orderCreateVO) {
log.info("开始创建订单: {}", orderCreateVO);
// 参数校验
if (ObjectUtils.isEmpty(orderCreateVO)) {
throw new IllegalArgumentException("订单参数不能为空");
}
if (orderCreateVO.getUserId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
if (orderCreateVO.getProductId() == null) {
throw new IllegalArgumentException("商品ID不能为空");
}
if (orderCreateVO.getQuantity() == null || orderCreateVO.getQuantity() <= 0) {
throw new IllegalArgumentException("购买数量必须大于0");
}
if (orderCreateVO.getAmount() == null || orderCreateVO.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("订单金额必须大于0");
}
// 检查库存
Inventory inventory = inventoryService.getInventoryByProductId(orderCreateVO.getProductId());
if (ObjectUtils.isEmpty(inventory) || inventory.getQuantity() < orderCreateVO.getQuantity()) {
throw new RuntimeException("商品库存不足");
}
// 创建订单
Order order = new Order();
order.setUserId(orderCreateVO.getUserId());
order.setProductId(orderCreateVO.getProductId());
order.setAmount(orderCreateVO.getAmount());
order.setStatus(0); // 待支付
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
int insert = baseMapper.insert(order);
if (insert <= 0) {
throw new RuntimeException("创建订单失败");
}
log.info("订单创建成功,订单ID: {}", order.getId());
// 扣减库存 - 依赖库存服务
boolean deductResult = inventoryService.deductInventory(
orderCreateVO.getProductId(),
orderCreateVO.getQuantity(),
order.getId()
);
if (!deductResult) {
throw new RuntimeException("扣减库存失败,订单创建回滚");
}
return order.getId();
}
/**
* 根据订单ID查询订单
*
* @param orderId 订单ID
* @return 订单信息
*/
@Override
public Order getOrderById(Long orderId) {
if (orderId == null) {
return null;
}
return baseMapper.selectById(orderId);
}
/**
* 取消订单
*
* @param orderId 订单ID
* @return 是否取消成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelOrder(Long orderId) {
log.info("开始取消订单,订单ID: {}", orderId);
if (orderId == null) {
log.error("订单ID不能为空");
return false;
}
Order order = baseMapper.selectById(orderId);
if (ObjectUtils.isEmpty(order)) {
log.error("订单不存在,订单ID: {}", orderId);
return false;
}
// 只有待支付状态的订单可以取消
if (order.getStatus() != 0) {
log.error("订单状态不允许取消,订单ID: {}, 状态: {}", orderId, order.getStatus());
return false;
}
// 更新订单状态为已取消
order.setStatus(2);
order.setUpdateTime(LocalDateTime.now());
int update = baseMapper.updateById(order);
if (update <= 0) {
log.error("更新订单状态失败,订单ID: {}", orderId);
return false;
}
// 查询库存信息,准备恢复库存
Inventory inventory = inventoryService.getInventoryByProductId(order.getProductId());
if (ObjectUtils.isEmpty(inventory)) {
log.error("商品库存不存在,商品ID: {}", order.getProductId());
return false;
}
// 恢复库存 - 依赖库存服务
boolean restoreResult = inventoryService.restoreInventory(
order.getProductId(),
1, // 简化示例,实际应从订单详情获取数量
orderId
);
if (!restoreResult) {
log.error("恢复库存失败,订单ID: {}", orderId);
throw new RuntimeException("恢复库存失败,订单取消回滚");
}
log.info("订单取消成功,订单ID: {}", orderId);
return true;
}
}
package com.ecommerce.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ecommerce.entity.Inventory;
import com.ecommerce.entity.Order;
import com.ecommerce.mapper.InventoryMapper;
import com.ecommerce.service.InventoryService;
import com.ecommerce.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
/**
* 库存服务实现类(存在循环依赖)
*
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements InventoryService {
/**
* 依赖订单服务 - 形成循环依赖的关键点
*/
private final OrderService orderService;
/**
* 扣减库存
*
* @param productId 商品ID
* @param quantity 数量
* @param orderId 订单ID
* @return 是否扣减成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deductInventory(Long productId, Integer quantity, Long orderId) {
log.info("开始扣减库存,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
if (productId == null || quantity == null || quantity <= 0 || orderId == null) {
log.error("扣减库存参数无效,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
return false;
}
// 验证订单是否存在 - 依赖订单服务
Order order = orderService.getOrderById(orderId);
if (ObjectUtils.isEmpty(order)) {
log.error("订单不存在,无法扣减库存,订单ID: {}", orderId);
return false;
}
// 验证订单商品与库存商品是否一致
if (!productId.equals(order.getProductId())) {
log.error("订单商品与库存商品不一致,订单ID: {}, 订单商品ID: {}, 库存商品ID: {}",
orderId, order.getProductId(), productId);
return false;
}
// 扣减库存(使用乐观锁思想,通过版本号或条件更新确保并发安全)
LambdaUpdateWrapper<Inventory> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Inventory::getProductId, productId)
.ge(Inventory::getQuantity, quantity)
.setSql("quantity = quantity - " + quantity)
.setSql("locked_quantity = locked_quantity + " + quantity)
.set(Inventory::getLastOrderId, orderId)
.set(Inventory::getUpdateTime, java.time.LocalDateTime.now());
int update = baseMapper.update(null, updateWrapper);
if (update <= 0) {
log.error("扣减库存失败,商品ID: {}, 可能库存不足", productId);
return false;
}
log.info("扣减库存成功,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
return true;
}
/**
* 恢复库存
*
* @param productId 商品ID
* @param quantity 数量
* @param orderId 订单ID
* @return 是否恢复成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean restoreInventory(Long productId, Integer quantity, Long orderId) {
log.info("开始恢复库存,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
if (productId == null || quantity == null || quantity <= 0 || orderId == null) {
log.error("恢复库存参数无效,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
return false;
}
// 验证订单是否存在 - 依赖订单服务
Order order = orderService.getOrderById(orderId);
if (ObjectUtils.isEmpty(order)) {
log.error("订单不存在,无法恢复库存,订单ID: {}", orderId);
return false;
}
// 恢复库存
LambdaUpdateWrapper<Inventory> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Inventory::getProductId, productId)
.ge(Inventory::getLockedQuantity, quantity)
.setSql("quantity = quantity + " + quantity)
.setSql("locked_quantity = locked_quantity - " + quantity)
.set(Inventory::getLastOrderId, orderId)
.set(Inventory::getUpdateTime, java.time.LocalDateTime.now());
int update = baseMapper.update(null, updateWrapper);
if (update <= 0) {
log.error("恢复库存失败,商品ID: {}", productId);
return false;
}
log.info("恢复库存成功,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
return true;
}
/**
* 查询商品库存
*
* @param productId 商品ID
* @return 库存信息
*/
@Override
public Inventory getInventoryByProductId(Long productId) {
if (productId == null) {
return null;
}
return baseMapper.selectOne(new LambdaQueryWrapper<Inventory>()
.eq(Inventory::getProductId, productId));
}
}
循环依赖的具体表现
上述代码中,OrderServiceImpl
通过构造器注入了InventoryService
,而InventoryServiceImpl
也通过构造器注入了OrderService
,形成了典型的构造器循环依赖。
当 Spring 容器启动时,会出现如下错误:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderServiceImpl':
Unsatisfied dependency expressed through constructor parameter 0; nested exception is
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'inventoryServiceImpl':
Unsatisfied dependency expressed through constructor parameter 0; nested exception is
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderServiceImpl':
Requested bean is currently in creation: Is there an unresolvable circular reference?
错误原因很明确:Spring 在创建orderServiceImpl
时需要inventoryServiceImpl
,而创建inventoryServiceImpl
又需要orderServiceImpl
,形成了无法解开的循环。
循环依赖的解决方案对比
面对循环依赖,开发者通常有以下几种解决方案,但各有优劣:
方案 1:使用 @Lazy 注解延迟加载
在注入依赖时使用@Lazy
注解,让 Spring 创建一个代理对象而非真实对象,从而打破循环:
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Lazy
private final InventoryService inventoryService;
// ...
}
优点 :实现简单,无需修改业务逻辑缺点:只是延迟了依赖的初始化,并未真正解决耦合问题,可能导致后续调用时的空指针异常
方案 2:改用字段注入或 setter 注入
将构造器注入改为字段注入或 setter 注入,利用 Spring 的三级缓存解决循环依赖:
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Autowired
private InventoryService inventoryService;
// ...
}
优点 :实现简单,能解决 Spring 容器启动问题缺点:不符合依赖注入的最佳实践,隐藏了依赖关系,且仍然存在业务耦合
方案 3:引入事件驱动模式
通过事件发布 / 订阅机制解耦,服务间不直接依赖,而是通过事件通信:
// 订单服务发布事件
applicationContext.publishEvent(new OrderCreatedEvent(order));
// 库存服务监听事件
@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
// 处理订单创建事件,扣减库存
}
优点 :彻底解耦服务,符合面向对象设计原则缺点:需要设计事件体系,异步处理可能引入新的复杂性
方案 4:拆分中间服务(推荐)
将两个服务间相互依赖的逻辑抽取出来,形成独立的中间服务, original 服务只依赖中间服务,从而打破循环:

优点 :彻底解决循环依赖,明确职责边界,提高代码可维护性缺点:需要重构代码,可能涉及较大的代码变动
重构方案:拆分中间服务破解循环依赖
我们采用拆分中间服务的方案,将订单和库存服务间相互依赖的逻辑抽取到独立的中间服务中,彻底打破循环依赖。
重构思路与架构设计
- 识别循环依赖点:找出两个服务间相互调用的方法
- 抽取共享逻辑:将这些相互依赖的逻辑抽取到中间服务
- 重新定义依赖关系:让订单服务和库存服务都依赖中间服务,而非相互依赖
- 调整业务流程:修改原有业务流程,通过中间服务完成交互
重构后的架构如下:

中间服务设计与实现
数据传输对象(DTO)
首先定义中间服务需要的数据传输对象:
package com.ecommerce.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* 订单库存关联DTO
*
* @author ken
*/
@Data
@Schema(description = "订单库存关联信息")
public class OrderInventoryDTO {
/**
* 订单ID
*/
@Schema(description = "订单ID")
private Long orderId;
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 商品ID
*/
@Schema(description = "商品ID")
private Long productId;
/**
* 商品数量
*/
@Schema(description = "商品数量")
private Integer quantity;
/**
* 订单金额
*/
@Schema(description = "订单金额")
private BigDecimal amount;
/**
* 操作类型:1-扣减库存 2-恢复库存
*/
@Schema(description = "操作类型:1-扣减库存 2-恢复库存")
private Integer operationType;
}
中间服务接口
package com.ecommerce.service;
import com.ecommerce.dto.OrderInventoryDTO;
import com.ecommerce.entity.Order;
import com.ecommerce.entity.Inventory;
/**
* 订单库存中间服务接口
* 用于处理订单和库存之间的交互逻辑,解决循环依赖问题
*
* @author ken
*/
public interface OrderInventoryMediatorService {
/**
* 检查库存并创建订单,同时扣减库存
*
* @param orderInventoryDTO 订单库存关联信息
* @return 订单ID
*/
Long createOrderWithInventoryCheck(OrderInventoryDTO orderInventoryDTO);
/**
* 取消订单并恢复库存
*
* @param orderId 订单ID
* @return 是否操作成功
*/
boolean cancelOrderAndRestoreInventory(Long orderId);
/**
* 根据订单ID查询订单信息(仅包含库存操作所需字段)
*
* @param orderId 订单ID
* @return 订单信息
*/
Order getOrderForInventory(Long orderId);
/**
* 根据商品ID查询库存信息(仅包含订单操作所需字段)
*
* @param productId 商品ID
* @return 库存信息
*/
Inventory getInventoryForOrder(Long productId);
}
中间服务实现
package com.ecommerce.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ecommerce.dto.OrderInventoryDTO;
import com.ecommerce.entity.Inventory;
import com.ecommerce.entity.Order;
import com.ecommerce.mapper.InventoryMapper;
import com.ecommerce.mapper.OrderMapper;
import com.ecommerce.service.OrderInventoryMediatorService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.time.LocalDateTime;
/**
* 订单库存中间服务实现类
*
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderInventoryMediatorServiceImpl implements OrderInventoryMediatorService {
private final OrderMapper orderMapper;
private final InventoryMapper inventoryMapper;
/**
* 检查库存并创建订单,同时扣减库存
*
* @param orderInventoryDTO 订单库存关联信息
* @return 订单ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long createOrderWithInventoryCheck(OrderInventoryDTO orderInventoryDTO) {
log.info("开始处理订单与库存关联操作: {}", orderInventoryDTO);
// 参数校验
if (ObjectUtils.isEmpty(orderInventoryDTO)) {
throw new IllegalArgumentException("订单库存关联信息不能为空");
}
if (orderInventoryDTO.getUserId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
if (orderInventoryDTO.getProductId() == null) {
throw new IllegalArgumentException("商品ID不能为空");
}
if (orderInventoryDTO.getQuantity() == null || orderInventoryDTO.getQuantity() <= 0) {
throw new IllegalArgumentException("商品数量必须大于0");
}
// 检查库存
Inventory inventory = getInventoryForOrder(orderInventoryDTO.getProductId());
if (ObjectUtils.isEmpty(inventory) || inventory.getQuantity() < orderInventoryDTO.getQuantity()) {
throw new RuntimeException("商品库存不足,商品ID: " + orderInventoryDTO.getProductId());
}
// 创建订单
Order order = new Order();
order.setUserId(orderInventoryDTO.getUserId());
order.setProductId(orderInventoryDTO.getProductId());
order.setAmount(orderInventoryDTO.getAmount());
order.setStatus(0); // 待支付
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
int insertOrder = orderMapper.insert(order);
if (insertOrder <= 0) {
throw new RuntimeException("创建订单失败");
}
log.info("订单创建成功,订单ID: {}", order.getId());
// 扣减库存
LambdaUpdateWrapper<Inventory> inventoryUpdateWrapper = new LambdaUpdateWrapper<>();
inventoryUpdateWrapper.eq(Inventory::getProductId, orderInventoryDTO.getProductId())
.ge(Inventory::getQuantity, orderInventoryDTO.getQuantity())
.setSql("quantity = quantity - " + orderInventoryDTO.getQuantity())
.setSql("locked_quantity = locked_quantity + " + orderInventoryDTO.getQuantity())
.set(Inventory::getLastOrderId, order.getId())
.set(Inventory::getUpdateTime, LocalDateTime.now());
int updateInventory = inventoryMapper.update(null, inventoryUpdateWrapper);
if (updateInventory <= 0) {
throw new RuntimeException("扣减库存失败,商品ID: " + orderInventoryDTO.getProductId());
}
log.info("库存扣减成功,商品ID: {}, 订单ID: {}", orderInventoryDTO.getProductId(), order.getId());
return order.getId();
}
/**
* 取消订单并恢复库存
*
* @param orderId 订单ID
* @return 是否操作成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelOrderAndRestoreInventory(Long orderId) {
log.info("开始处理取消订单并恢复库存,订单ID: {}", orderId);
if (orderId == null) {
log.error("订单ID不能为空");
return false;
}
// 查询订单信息
Order order = getOrderForInventory(orderId);
if (ObjectUtils.isEmpty(order)) {
log.error("订单不存在,订单ID: {}", orderId);
return false;
}
// 检查订单状态,只有待支付状态可以取消
if (order.getStatus() != 0) {
log.error("订单状态不允许取消,订单ID: {}, 状态: {}", orderId, order.getStatus());
return false;
}
// 更新订单状态为已取消
Order updateOrder = new Order();
updateOrder.setId(orderId);
updateOrder.setStatus(2); // 已取消
updateOrder.setUpdateTime(LocalDateTime.now());
int updateOrderResult = orderMapper.updateById(updateOrder);
if (updateOrderResult <= 0) {
log.error("更新订单状态失败,订单ID: {}", orderId);
return false;
}
log.info("订单状态更新为已取消,订单ID: {}", orderId);
// 恢复库存
LambdaUpdateWrapper<Inventory> inventoryUpdateWrapper = new LambdaUpdateWrapper<>();
inventoryUpdateWrapper.eq(Inventory::getProductId, order.getProductId())
.ge(Inventory::getLockedQuantity, 1) // 简化示例,实际应根据订单数量恢复
.setSql("quantity = quantity + 1")
.setSql("locked_quantity = locked_quantity - 1")
.set(Inventory::getLastOrderId, orderId)
.set(Inventory::getUpdateTime, LocalDateTime.now());
int updateInventoryResult = inventoryMapper.update(null, inventoryUpdateWrapper);
if (updateInventoryResult <= 0) {
log.error("恢复库存失败,商品ID: {}", order.getProductId());
return false;
}
log.info("库存恢复成功,商品ID: {}, 订单ID: {}", order.getProductId(), orderId);
return true;
}
/**
* 根据订单ID查询订单信息(仅包含库存操作所需字段)
*
* @param orderId 订单ID
* @return 订单信息
*/
@Override
public Order getOrderForInventory(Long orderId) {
if (orderId == null) {
return null;
}
return orderMapper.selectById(orderId);
}
/**
* 根据商品ID查询库存信息(仅包含订单操作所需字段)
*
* @param productId 商品ID
* @return 库存信息
*/
@Override
public Inventory getInventoryForOrder(Long productId) {
if (productId == null) {
return null;
}
return inventoryMapper.selectOne(new LambdaQueryWrapper<Inventory>()
.eq(Inventory::getProductId, productId));
}
}
重构订单服务和库存服务
重构后的订单服务
package com.ecommerce.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ecommerce.dto.OrderInventoryDTO;
import com.ecommerce.entity.Order;
import com.ecommerce.mapper.OrderMapper;
import com.ecommerce.service.OrderService;
import com.ecommerce.service.OrderInventoryMediatorService;
import com.ecommerce.vo.OrderCreateVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
/**
* 订单服务实现类(重构后,无循环依赖)
*
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
/**
* 依赖中间服务,而非直接依赖库存服务
*/
private final OrderInventoryMediatorService orderInventoryMediatorService;
/**
* 创建订单
*
* @param orderCreateVO 订单创建参数
* @return 订单ID
*/
@Override
public Long createOrder(OrderCreateVO orderCreateVO) {
log.info("开始创建订单: {}", orderCreateVO);
// 参数转换
OrderInventoryDTO dto = new OrderInventoryDTO();
dto.setUserId(orderCreateVO.getUserId());
dto.setProductId(orderCreateVO.getProductId());
dto.setQuantity(orderCreateVO.getQuantity());
dto.setAmount(orderCreateVO.getAmount());
dto.setOperationType(1); // 扣减库存
// 调用中间服务处理订单创建和库存扣减
return orderInventoryMediatorService.createOrderWithInventoryCheck(dto);
}
/**
* 根据订单ID查询订单
*
* @param orderId 订单ID
* @return 订单信息
*/
@Override
public Order getOrderById(Long orderId) {
if (orderId == null) {
return null;
}
return baseMapper.selectById(orderId);
}
/**
* 取消订单
*
* @param orderId 订单ID
* @return 是否取消成功
*/
@Override
public boolean cancelOrder(Long orderId) {
log.info("开始取消订单,订单ID: {}", orderId);
// 调用中间服务处理订单取消和库存恢复
return orderInventoryMediatorService.cancelOrderAndRestoreInventory(orderId);
}
}
重构后的库存服务
package com.ecommerce.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ecommerce.entity.Inventory;
import com.ecommerce.mapper.InventoryMapper;
import com.ecommerce.service.InventoryService;
import com.ecommerce.service.OrderInventoryMediatorService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
/**
* 库存服务实现类(重构后,无循环依赖)
*
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements InventoryService {
/**
* 依赖中间服务,而非直接依赖订单服务
*/
private final OrderInventoryMediatorService orderInventoryMediatorService;
/**
* 扣减库存
* 注意:重构后,此方法仅处理单纯的库存扣减,不涉及订单验证
* 涉及订单的库存扣减应通过中间服务处理
*
* @param productId 商品ID
* @param quantity 数量
* @param orderId 订单ID
* @return 是否扣减成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deductInventory(Long productId, Integer quantity, Long orderId) {
log.info("开始扣减库存,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
if (productId == null || quantity == null || quantity <= 0) {
log.error("扣减库存参数无效,商品ID: {}, 数量: {}", productId, quantity);
return false;
}
// 扣减库存
LambdaUpdateWrapper<Inventory> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Inventory::getProductId, productId)
.ge(Inventory::getQuantity, quantity)
.setSql("quantity = quantity - " + quantity)
.setSql("locked_quantity = locked_quantity + " + quantity);
// 订单ID非空时才更新
if (orderId != null) {
updateWrapper.set(Inventory::getLastOrderId, orderId);
}
updateWrapper.set(Inventory::getUpdateTime, java.time.LocalDateTime.now());
int update = baseMapper.update(null, updateWrapper);
if (update <= 0) {
log.error("扣减库存失败,商品ID: {}", productId);
return false;
}
log.info("扣减库存成功,商品ID: {}, 数量: {}", productId, quantity);
return true;
}
/**
* 恢复库存
* 注意:重构后,此方法仅处理单纯的库存恢复,不涉及订单验证
* 涉及订单的库存恢复应通过中间服务处理
*
* @param productId 商品ID
* @param quantity 数量
* @param orderId 订单ID
* @return 是否恢复成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean restoreInventory(Long productId, Integer quantity, Long orderId) {
log.info("开始恢复库存,商品ID: {}, 数量: {}, 订单ID: {}", productId, quantity, orderId);
if (productId == null || quantity == null || quantity <= 0) {
log.error("恢复库存参数无效,商品ID: {}, 数量: {}", productId, quantity);
return false;
}
// 恢复库存
LambdaUpdateWrapper<Inventory> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Inventory::getProductId, productId)
.ge(Inventory::getLockedQuantity, quantity)
.setSql("quantity = quantity + " + quantity)
.setSql("locked_quantity = locked_quantity - " + quantity);
// 订单ID非空时才更新
if (orderId != null) {
updateWrapper.set(Inventory::getLastOrderId, orderId);
}
updateWrapper.set(Inventory::getUpdateTime, java.time.LocalDateTime.now());
int update = baseMapper.update(null, updateWrapper);
if (update <= 0) {
log.error("恢复库存失败,商品ID: {}", productId);
return false;
}
log.info("恢复库存成功,商品ID: {}, 数量: {}", productId, quantity);
return true;
}
/**
* 查询商品库存
*
* @param productId 商品ID
* @return 库存信息
*/
@Override
public Inventory getInventoryByProductId(Long productId) {
if (productId == null) {
return null;
}
return baseMapper.selectOne(new LambdaQueryWrapper<Inventory>()
.eq(Inventory::getProductId, productId));
}
}
重构前后对比分析
依赖关系对比
重构前的依赖关系:

重构后的依赖关系:
代码结构对比
对比维度 | 重构前 | 重构后 |
---|---|---|
依赖关系 | 循环依赖,服务间耦合紧密 | 单向依赖,服务间解耦 |
职责边界 | 模糊,服务承担过多职责 | 清晰,各服务专注于自身核心业务 |
可维护性 | 低,修改一个服务可能影响另一个 | 高,服务间影响范围可控 |
可测试性 | 低,需同时初始化所有依赖服务 | 高,可单独测试每个服务 |
扩展性 | 差,难以单独扩展某个服务 | 好,可独立扩展各服务功能 |
业务流程对比
创建订单的流程对比:
重构前:

重构后:
中间服务的最佳实践与设计原则
单一职责原则
中间服务应专注于处理特定的跨服务交互逻辑,不应承担与核心职责无关的功能。例如,OrderInventoryMediatorService
只处理订单和库存间的交互,不涉及用户管理或支付流程。
依赖倒置原则
中间服务应依赖抽象而非具体实现,通过接口定义交互契约,便于后续替换实现或扩展功能。
最小知识原则
中间服务应尽量减少对其他服务内部实现的了解,只通过公开接口进行交互,降低耦合度。
事务边界清晰
中间服务通常需要处理跨服务的事务,应明确事务边界,必要时使用分布式事务保证数据一致性。
避免过度设计
不是所有的跨服务交互都需要中间服务,只有当出现循环依赖或职责不清时才考虑引入,避免为了设计而设计。
项目配置与测试验证
Maven 依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/>
</parent>
<groupId>com.ecommerce</groupId>
<artifactId>order-inventory-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order-inventory-service</name>
<description>电商订单与库存服务(解决循环依赖示例)</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<lombok.version>1.18.30</lombok.version>
<fastjson2.version>2.0.46</fastjson2.version>
</properties>
<dependencies>
<!-- Spring Boot核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- API文档 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>${mybatis-plus.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件
spring:
application:
name: order-inventory-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ecommerce?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
server:
port: 8080
servlet:
context-path: /api
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
operationsSorter: method
logging:
level:
com.ecommerce: info
数据库初始化脚本
CREATE DATABASE IF NOT EXISTS ecommerce DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ecommerce;
-- 订单表
CREATE TABLE IF NOT EXISTS t_order (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态:0-待支付 1-已支付 2-已取消 3-已完成',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0-未删除 1-已删除',
INDEX idx_user_id (user_id),
INDEX idx_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- 库存表
CREATE TABLE IF NOT EXISTS t_inventory (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '库存ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
quantity INT NOT NULL DEFAULT 0 COMMENT '库存数量',
locked_quantity INT NOT NULL DEFAULT 0 COMMENT '锁定数量',
last_order_id BIGINT NULL COMMENT '最后操作订单ID',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0-未删除 1-已删除',
UNIQUE KEY uk_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';
-- 初始化测试数据
INSERT INTO t_inventory (product_id, quantity, locked_quantity) VALUES (1001, 100, 0);
INSERT INTO t_inventory (product_id, quantity, locked_quantity) VALUES (1002, 50, 0);
测试代码
package com.ecommerce.service;
import com.ecommerce.EcommerceApplication;
import com.ecommerce.entity.Inventory;
import com.ecommerce.entity.Order;
import com.ecommerce.vo.OrderCreateVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;
import java.math.BigDecimal;
/**
* 订单与库存服务测试(验证循环依赖是否解决)
*
* @author ken
*/
@SpringBootTest(classes = EcommerceApplication.class)
public class OrderInventoryServiceTest {
@Autowired
private OrderService orderService;
@Autowired
private InventoryService inventoryService;
/**
* 测试创建订单流程
*/
@Test
public void testCreateOrder() {
// 准备测试数据
OrderCreateVO orderCreateVO = new OrderCreateVO();
orderCreateVO.setUserId(1L);
orderCreateVO.setProductId(1001L);
orderCreateVO.setQuantity(5);
orderCreateVO.setAmount(new BigDecimal("99.95"));
// 执行创建订单操作
Long orderId = orderService.createOrder(orderCreateVO);
Assert.notNull(orderId, "订单创建失败");
System.out.println("创建订单成功,订单ID: " + orderId);
// 验证订单是否创建成功
Order order = orderService.getOrderById(orderId);
Assert.notNull(order, "订单不存在");
Assert.isTrue(order.getStatus() == 0, "订单状态不正确");
Assert.isTrue(order.getProductId().equals(1001L), "商品ID不正确");
// 验证库存是否扣减成功
Inventory inventory = inventoryService.getInventoryByProductId(1001L);
Assert.notNull(inventory, "库存不存在");
Assert.isTrue(inventory.getQuantity() == 95, "库存扣减不正确");
Assert.isTrue(inventory.getLockedQuantity() == 5, "库存锁定不正确");
}
/**
* 测试取消订单流程
*/
@Test
public void testCancelOrder() {
// 先创建一个订单
OrderCreateVO orderCreateVO = new OrderCreateVO();
orderCreateVO.setUserId(1L);
orderCreateVO.setProductId(1001L);
orderCreateVO.setQuantity(3);
orderCreateVO.setAmount(new BigDecimal("59.97"));
Long orderId = orderService.createOrder(orderCreateVO);
Assert.notNull(orderId, "订单创建失败");
// 执行取消订单操作
boolean cancelResult = orderService.cancelOrder(orderId);
Assert.isTrue(cancelResult, "订单取消失败");
// 验证订单状态是否更新
Order order = orderService.getOrderById(orderId);
Assert.notNull(order, "订单不存在");
Assert.isTrue(order.getStatus() == 2, "订单状态未更新为已取消");
// 验证库存是否恢复
Inventory inventory = inventoryService.getInventoryByProductId(1001L);
Assert.notNull(inventory, "库存不存在");
Assert.isTrue(inventory.getQuantity() == 97, "库存未恢复"); // 100 - 3 + 3 = 100? 注意:前面的测试可能已扣减了5个
Assert.isTrue(inventory.getLockedQuantity() == 0, "库存锁定未解除");
}
}
总结与扩展
循环依赖是 Java 开发中常见的设计问题,它不仅会导致技术上的问题(如系统启动失败),更反映了代码设计上的缺陷(如职责不清、耦合过紧)。本文通过一个电商系统的真实案例,详细讲解了循环依赖的产生原因和解决方案,重点介绍了如何通过拆分中间服务来彻底解决循环依赖问题。