从 “死锁“ 到 “解耦“:重构中间服务破解 Java 循环依赖难题

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

循环依赖的本质与危害

什么是循环依赖

循环依赖指的是两个或多个组件之间相互依赖,形成一个封闭的依赖环。在 Java Spring 框架中,最常见的是构造器循环依赖字段循环依赖两种形式。

如上图所示,订单服务依赖库存服务进行库存扣减,而库存服务又依赖订单服务查询相关订单信息,形成了典型的双向循环依赖。

循环依赖的危害

  1. 系统启动失败 :Spring 容器在初始化具有循环依赖的 Bean 时,若未采用合适的注入方式(如构造器注入),会直接抛出BeanCurrentlyInCreationException异常。

  2. 代码可读性下降:循环依赖会使代码结构变得混乱,新接手的开发者难以理清模块间的调用关系。

  3. 维护成本增加:修改一个服务可能会波及多个依赖它的服务,形成 "牵一发而动全身" 的局面。

  4. 测试困难:循环依赖的组件难以进行单元测试,必须同时初始化所有相关依赖才能完成测试。

  5. 扩展性受限:存在循环依赖的模块往往耦合紧密,难以单独进行扩展或替换。

Spring 对循环依赖的处理机制

Spring 容器对循环依赖的处理能力有限,仅支持单例 Bean 的字段注入setter 注入的循环依赖,这是通过三级缓存实现的:

  1. 一级缓存:存储完全初始化好的 Bean
  2. 二级缓存:存储早期暴露的 Bean 实例(未完全初始化)
  3. 三级缓存:存储 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 服务只依赖中间服务,从而打破循环:

优点 :彻底解决循环依赖,明确职责边界,提高代码可维护性缺点:需要重构代码,可能涉及较大的代码变动

重构方案:拆分中间服务破解循环依赖

我们采用拆分中间服务的方案,将订单和库存服务间相互依赖的逻辑抽取到独立的中间服务中,彻底打破循环依赖。

重构思路与架构设计

  1. 识别循环依赖点:找出两个服务间相互调用的方法
  2. 抽取共享逻辑:将这些相互依赖的逻辑抽取到中间服务
  3. 重新定义依赖关系:让订单服务和库存服务都依赖中间服务,而非相互依赖
  4. 调整业务流程:修改原有业务流程,通过中间服务完成交互

重构后的架构如下:

中间服务设计与实现

数据传输对象(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 开发中常见的设计问题,它不仅会导致技术上的问题(如系统启动失败),更反映了代码设计上的缺陷(如职责不清、耦合过紧)。本文通过一个电商系统的真实案例,详细讲解了循环依赖的产生原因和解决方案,重点介绍了如何通过拆分中间服务来彻底解决循环依赖问题。

相关推荐
whltaoin3 小时前
Java 后端与 AI 融合:技术路径、实战案例与未来趋势
java·开发语言·人工智能·编程思想·ai生态
00后程序员张4 小时前
RabbitMQ核心机制
java·大数据·分布式
用户0332126663674 小时前
将 HTML 转换为 Word:Java 自动化文档生成
java
天天摸鱼的java工程师4 小时前
Java 版 “国庆头像生成器”:8 年老开发的实用小工具
java·后端
亦良Cool4 小时前
如何部署一个Java项目
java·开发语言
徐子童4 小时前
优选算法---字符串
java·算法·字符串·笔试·高精度相乘
自由的疯4 小时前
java调chrome浏览器显示网页
java·前端·后端
kfepiza4 小时前
Spring 如何解决循环依赖 笔记251008
java·spring boot·spring