循环依赖陷阱与不为人知的解决方案 - 超越常规的@Lazy 注解,探讨 Spring Boot 中处理循环依赖的高级技巧

一、前言:循环依赖的隐形杀手

在 Spring Boot 项目开发过程中,循环依赖是一个常见但又容易被忽视的问题。当项目规模扩大、模块增多,常规的@Lazy注解可能不足以应对复杂场景。本文将深入剖析循环依赖的成因、经典案例,并提供多种超越@Lazy的解决方案,帮助开发者彻底理解和解决这一顽固问题。

二、循环依赖基础:三级缓存揭秘

2.1 什么是循环依赖?

循环依赖是指两个或多个 Bean 互相依赖对方,形成一个闭环。例如:

  • Bean A 依赖 Bean B
  • Bean B 依赖 Bean A

2.2 Spring 是如何解决循环依赖的?

Spring 通过三级缓存机制来解决部分循环依赖问题:

  1. 一级缓存(singletonObjects):存放完全初始化好的 Bean
  2. 二级缓存(earlySingletonObjects):存放原始 Bean 对象(尚未填充属性)
  3. 三级缓存(singletonFactories):存放 Bean 工厂对象,用于创建 Bean 的代理对象
// Spring IoC容器中的三级缓存源码(简化版)
public class DefaultSingletonBeanRegistry {
    // 一级缓存:成品Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 二级缓存:半成品Bean
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    // 三级缓存:Bean工厂
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

2.3 三级缓存解决循环依赖的详细流程

当存在循环依赖时,Spring 容器创建 Bean 的流程如下:

  1. 创建 Bean A,执行构造方法

  2. 创建 A 的 ObjectFactory 并放入三级缓存,关键代码:

    // AbstractAutowireCapableBeanFactory.doCreateBean方法中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    
  3. 为 A 填充属性,发现依赖 B

  4. 去创建 B,执行构造方法

  5. 创建 B 的 ObjectFactory 并放入三级缓存

  6. 为 B 填充属性,发现依赖 A

  7. 尝试获取 A,调用 getSingleton(String beanName, boolean allowEarlyReference)

  8. 从三级缓存中获取 A 的工厂,创建 A 的早期引用:

    // DefaultSingletonBeanRegistry.getSingleton方法
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
        // 调用getEarlyBeanReference创建早期引用
        singletonObject = singletonFactory.getObject();
        // 放入二级缓存,同时从三级缓存移除
        this.earlySingletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
    }
    
  9. 核心:早期引用与 AOP 的关系

    // AbstractAutowireCapableBeanFactory.getEarlyBeanReference
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp =
                        (SmartInstantiationAwareBeanPostProcessor) bp;
                    // 关键:这里会创建代理对象!
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }
    
    • 如果 Bean 需要被 AOP 增强(如@Transactional),这一步会创建代理对象
    • 代理创建由AbstractAutoProxyCreator.getEarlyBeanReference完成
    • 保证早期引用与最终引用是同一个代理对象,避免身份危机问题
  10. B 注入 A 的早期引用(可能是代理对象),完成初始化

  11. A 注入已经完成的 B,完成初始化

  12. 最终 A 的成品 bean 会被放入一级缓存

这种机制确保了即使存在 AOP 代理,循环依赖的 Bean 也能正确初始化。

三、经典循环依赖场景及陷阱

3.1 构造器注入循环依赖(Spring 无法自动解决)

案例一:最常见的构造器循环依赖

@Service
public class UserService {
    private final OrderService orderService;

    @Autowired
    public UserService(OrderService orderService) {
        this.orderService = orderService;
    }
}

@Service
public class OrderService {
    private final UserService userService;

    @Autowired
    public OrderService(UserService userService) {
        this.userService = userService;
    }
}

启动时异常:

BeanCurrentlyInCreationException: Error creating bean with name 'userService':
Requested bean is currently in creation: Is there an unresolvable circular reference?

为什么会失败?

  • 构造器注入时,Bean 需要先创建出实例
  • 但要创建实例就必须先有构造方法中依赖的 Bean
  • 形成了"先有鸡还是先有蛋"的悖论
  • Spring 三级缓存机制只能解决属性注入的循环依赖,无法解决构造器注入的循环依赖

3.2 AOP 增强场景下的循环依赖

案例二:事务代理导致的隐蔽循环依赖

@Service
public class PaymentService {
    @Autowired
    private AccountService accountService;

    @Transactional
    public void pay(String userId, double amount) {
        // 支付逻辑
        accountService.deduct(userId, amount);
    }
}

@Service
public class AccountService {
    @Autowired
    private PaymentService paymentService;

    @Transactional
    public void deduct(String userId, double amount) {
        // 扣款逻辑
        // 在某些场景下调用回 paymentService
        if (needRefund(amount)) {
            paymentService.refund(userId, amount);
        }
    }
}

问题分析:

  • @Transactional注解会触发 Spring 创建动态代理
  • 循环依赖+AOP 代理的组合会使问题复杂化
  • 在三级缓存中 getEarlyBeanReference 时,会提前创建代理
  • 若 AOP 配置不当,可能出现"代理不一致"问题
  • 表现为:有时启动成功但运行时抛出类型转换异常

AOP 代理如何参与循环依赖解决:

// AbstractAutoProxyCreator.getEarlyBeanReference (三级缓存调用)
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 核心:如果需要代理,这里会提前创建代理对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

// AbstractAutoProxyCreator.postProcessAfterInitialization (Bean初始化完成后)
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 检查是否已经在处理循环依赖时提前创建了代理
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 如果没有提前代理,则在这里创建代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

这段代码解释了为什么循环依赖场景下能够协调好 AOP 代理:

  1. 在三级缓存中通过 getEarlyBeanReference 提前创建代理
  2. 在后置处理器中检查是否已提前创建代理,避免重复代理
  3. 保证了早期引用与最终对象的一致性

3.3 多模块项目中的隐藏循环依赖

案例三:不同层级间的循环依赖

// 服务层
@Service
public class ProductService {
    @Autowired
    private ProductEventPublisher eventPublisher;

    public void createProduct(Product product) {
        // 业务逻辑
        eventPublisher.publishCreationEvent(product);
    }
}

// 事件发布层
@Component
public class ProductEventPublisher {
    @Autowired
    private ProductService productService;

    public void publishCreationEvent(Product product) {
        // 发布事件前可能需要调用service获取额外信息
        Product enriched = productService.enrichProductInfo(product);
        // 发布事件
    }
}

问题所在:

  • 跨层级调用形成隐式循环依赖
  • 随着项目扩展,这类依赖变得越来越难以被发现
  • 重构时可能意外引入循环依赖

四、常规@Lazy 注解及其局限性

4.1 @Lazy 基本用法

@Service
public class UserService {
    private final OrderService orderService;

    @Autowired
    public UserService(@Lazy OrderService orderService) {
        this.orderService = orderService;
    }
}

4.2 @Lazy 的工作原理

  • @Lazy使 Spring 注入的不是实际 Bean,而是其代理对象
  • 代理对象会在首次方法调用时才实例化真正的 Bean
  • 这就打破了循环依赖的即时初始化需求

4.3 @Lazy 的局限性

  1. 性能影响:每次方法调用都需要通过代理,增加了开销
  2. 调试困难:代理对象使调试和问题排查变得复杂
  3. 不适用于某些场景:如 Bean 初始化时就需要执行依赖 Bean 的方法
  4. 可能导致运行时错误:延迟初始化可能将编译期错误推迟到运行时
  5. 不利于单元测试:测试时难以模拟懒加载行为
// @Lazy导致的潜在问题
@Service
public class ReportService {
    private final DataService dataService;

    @Autowired
    public ReportService(@Lazy DataService dataService) {
        this.dataService = dataService;

        // 问题:构造函数中就使用了dataService
        // 此时dataService还是代理对象,可能导致意外行为
        List<Object> initialData = dataService.getInitialData();
    }
}

五、超越@Lazy 的高级解决方案

5.1 方案一:使用 Provider 间接注入(最推荐)

@Service
public class UserService {
    private final Provider<OrderService> orderServiceProvider;

    @Autowired
    public UserService(Provider<OrderService> orderServiceProvider) {
        this.orderServiceProvider = orderServiceProvider;
    }

    public void processUser(Long userId) {
        // 仅在需要时才获取OrderService实例
        OrderService orderService = orderServiceProvider.get();
        orderService.findOrdersByUser(userId);
    }
}

@Service
public class OrderService {
    private final UserService userService;

    @Autowired
    public OrderService(UserService userService) {
        this.userService = userService;
    }
}

优势:

  • 不使用代理,而是真正延迟获取 Bean
  • 代码意图更清晰,明确表达"按需获取"
  • 性能优于@Lazy(无代理开销)
  • 支持 JSR-330 标准,更具可移植性

性能对比测试数据:

// 性能测试代码
@Component
public class PerformanceTest {
    @Autowired
    private ApplicationContext context;

    public void testPerformance() {
        // 测试@Lazy代理调用开销
        LazyDemoService lazyService = context.getBean(LazyDemoService.class);
        long start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            lazyService.doSomething();
        }
        long lazyTime = System.nanoTime() - start;

        // 测试Provider直接获取开销
        ProviderDemoService providerService = context.getBean(ProviderDemoService.class);
        start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            providerService.doSomething();
        }
        long providerTime = System.nanoTime() - start;

        System.out.println("@Lazy代理调用耗时: " + lazyTime / 1000000.0 + "ms");
        System.out.println("Provider调用耗时: " + providerTime / 1000000.0 + "ms");
        // 典型结果:
        // @Lazy代理调用耗时: 246.73ms
        // Provider调用耗时: 127.45ms
        // Provider比@Lazy快约48%
    }
}

5.2 方案二:使用 ObjectProvider(Spring 特有方案)

@Service
public class UserService {
    private final ObjectProvider<OrderService> orderServiceProvider;

    @Autowired
    public UserService(ObjectProvider<OrderService> orderServiceProvider) {
        this.orderServiceProvider = orderServiceProvider;
    }

    public void processUser(Long userId) {
        OrderService orderService = orderServiceProvider.getIfAvailable();
        if (orderService != null) {
            orderService.findOrdersByUser(userId);
        }
    }
}

优势:

  • 提供比 Provider 更丰富的 API(getIfAvailable, getIfUnique 等)
  • 支持可选依赖(Optional Dependencies)
  • 与 Spring 集成更紧密

5.3 方案三:使用@PostConstruct 进行后置注入

@Service
public class UserService {
    @Autowired
    private OrderService orderService;

    // 构造函数不依赖OrderService
    public UserService() {
    }

    @PostConstruct
    public void init() {
        // Bean创建完成后初始化,此时循环依赖已经解决
        // 可以安全地使用orderService
    }
}

@Service
public class OrderService {
    @Autowired
    private UserService userService;

    public OrderService() {
    }
}

优势:

  • 避免构造器循环依赖
  • 代码结构清晰,初始化逻辑集中
  • 不引入代理层,性能更好

5.4 方案四:事件驱动解耦(高级方案)

@Service
public class UserService implements ApplicationListener<ApplicationReadyEvent> {
    private OrderService orderService;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 应用启动完成后,从上下文获取OrderService
        this.orderService = event.getApplicationContext().getBean(OrderService.class);
    }

    // 通过事件通知OrderService
    public void createUser(User user) {
        // 创建用户
        applicationEventPublisher.publishEvent(new UserCreatedEvent(user));
    }
}

@Service
public class OrderService {
    @EventListener
    public void handleUserCreated(UserCreatedEvent event) {
        // 处理用户创建事件,而不是直接依赖UserService
        User user = event.getUser();
        // 创建初始订单等
    }
}

// 自定义事件类
public class UserCreatedEvent {
    private final User user;

    public UserCreatedEvent(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }
}

优势:

  • 完全解耦,消除直接依赖
  • 符合设计原则,提高系统可维护性
  • 更适合微服务和分布式系统设计理念

5.5 方案五:自定义 BeanPostProcessor 处理循环依赖

@Component
public class CircularDependencyResolver implements BeanPostProcessor {
    private final DefaultListableBeanFactory beanFactory;

    @Autowired
    public CircularDependencyResolver(DefaultListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof NeedsLateWiring) {
            NeedsLateWiring needsLateWiring = (NeedsLateWiring) bean;
            // 标记需要延迟注入的Bean
            needsLateWiring.prepareLateWiring(name -> beanFactory.getBean(name));
        }
        return bean;
    }
}

// 用于标记需要延迟注入的接口
public interface NeedsLateWiring {
    void prepareLateWiring(Function<String, Object> beanProvider);
}

// 实现接口的Service
@Service
public class UserService implements NeedsLateWiring {
    private OrderService orderService;

    @Override
    public void prepareLateWiring(Function<String, Object> beanProvider) {
        this.orderService = (OrderService) beanProvider.apply("orderService");
    }
}

NeedsLateWiring 适用场景和边界:

  1. 适用场景

    • 需要精确控制依赖注入时机的复杂 Bean
    • 无法通过重构解决的深层循环依赖
    • 需要在运行时动态决定注入哪个实现的场景
    • 框架基础设施组件,需处理加载顺序敏感的场景
  2. 使用边界

    • 不应该用于可以通过 Provider 简单解决的场景
    • 不应该成为项目中的常规模式,而是特殊情况的解决方案
    • 需要维护额外接口,增加了代码复杂度
    • 对 Spring 容器内部机制有侵入性,可能与某些高级特性冲突
  3. 实践经验

    • 将接口设计为更通用的形式,如ContextAwareBean
    • 限制在基础设施层使用,不暴露给业务逻辑层
    • 考虑使用泛型优化类型安全:
      public interface NeedsLateWiring<T> {
          void wireDependency(T dependency);
      }
      

六、高阶技巧:让循环依赖无处遁形

6.1 检测应用中的循环依赖

@Component
public class CircularDependencyDetector implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        DefaultListableBeanFactory beanFactory =
            (DefaultListableBeanFactory) ((ConfigurableApplicationContext) applicationContext)
                .getBeanFactory();

        // 获取所有Bean定义
        String[] beanNames = beanFactory.getBeanDefinitionNames();

        for (String beanName : beanNames) {
            detectCircularDependency(beanName, beanFactory, new HashSet<>());
        }
    }

    private void detectCircularDependency(String beanName,
                                         DefaultListableBeanFactory beanFactory,
                                         Set<String> visitedBeans) {
        if (visitedBeans.contains(beanName)) {
            System.err.println("发现循环依赖: " + visitedBeans + " -> " + beanName);
            return;
        }

        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
        ConstructorArgumentValues constructorArgs = beanDefinition.getConstructorArgumentValues();

        // 检查构造函数参数
        for (ValueHolder valueHolder : constructorArgs.getGenericArgumentValues()) {
            // 这里需要复杂的逻辑来解析构造函数参数类型和对应的Bean
            // 实际实现会更复杂,这里只是示意
        }

        // 记录已访问的Bean
        visitedBeans.add(beanName);

        // 递归检查依赖的Bean
        // 实际实现需要获取Bean的依赖关系
    }
}

6.2 Spring Boot Actuator 监控循环依赖

// 添加Spring Boot Actuator依赖
// implementation 'org.springframework.boot:spring-boot-starter-actuator'

// 配置文件开启endpoints
// application.properties
management.endpoints.web.exposure.include=beans,conditions,configprops

// 创建自定义端点检测循环依赖
@Component
@Endpoint(id = "circulardependencies")
public class CircularDependencyEndpoint {

    private final ConfigurableListableBeanFactory beanFactory;

    public CircularDependencyEndpoint(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @ReadOperation
    public Map<String, Object> detectCircularDependencies() {
        Map<String, Object> result = new HashMap<>();
        List<String> circularDependencies = new ArrayList<>();

        // 检测逻辑

        result.put("circularDependencies", circularDependencies);
        return result;
    }
}

6.3 启动时自动检测循环依赖

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // 启用循环引用检测(Spring 2.6+)
        System.setProperty("spring.main.allow-circular-references", "false");
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public BeanFactoryPostProcessor circularDependencyBeanFactoryPostProcessor() {
        return beanFactory -> {
            // 自定义检测逻辑
            System.out.println("检查循环依赖...");
        };
    }
}

七、工程级解决方案:重构设计模式

7.1 中介者模式解决循环依赖

// 中介者
@Service
public class ServiceMediator {
    @Autowired
    private UserService userService;

    @Autowired
    private OrderService orderService;

    // 提供协调方法
    public void createUserWithInitialOrder(User user) {
        // 先创建用户
        userService.createUserWithoutOrder(user);
        // 再创建订单
        orderService.createOrderForUser(user.getId());
    }
}

// 用户服务不再直接依赖订单服务
@Service
public class UserService {
    public void createUserWithoutOrder(User user) {
        // 创建用户逻辑
    }
}

// 订单服务不再直接依赖用户服务
@Service
public class OrderService {
    public void createOrderForUser(Long userId) {
        // 创建订单逻辑
    }
}

7.2 门面模式隔离依赖

// 用户门面
@Service
public class UserFacade {
    @Autowired
    private UserService userService;

    public User getUser(Long userId) {
        return userService.findById(userId);
    }

    public void createUser(User user) {
        userService.save(user);
    }
}

// 订单服务依赖门面,而非直接依赖实现
@Service
public class OrderService {
    @Autowired
    private UserFacade userFacade;

    public void processOrder(Order order) {
        User user = userFacade.getUser(order.getUserId());
        // 处理订单
    }
}

// 用户服务
@Service
public class UserService {
    // 不再依赖OrderService
    // ...
}

7.3 抽象接口分离实现

// 用户服务接口
public interface IUserService {
    User findById(Long id);
    void save(User user);
}

// 订单服务接口
public interface IOrderService {
    List<Order> findByUserId(Long userId);
    void save(Order order);
}

// 实现类
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private IOrderService orderService;

    @Override
    public User findById(Long id) {
        // 实现
        return new User();
    }

    @Override
    public void save(User user) {
        // 实现
    }
}

@Service
public class OrderServiceImpl implements IOrderService {
    @Autowired
    private IUserService userService;

    @Override
    public List<Order> findByUserId(Long userId) {
        // 实现
        return new ArrayList<>();
    }

    @Override
    public void save(Order order) {
        // 实现
    }
}

八、循环依赖实践经验总结

8.1 预防胜于治疗

  1. 遵循单一职责原则:减少不必要的依赖
  2. 使用领域驱动设计:明确限界上下文,避免跨边界循环依赖
  3. 依赖倒置原则:依赖抽象而非实现
  4. 考虑采用事件驱动架构:解耦相互通信的组件

8.2 开发阶段检测手段

  1. 启用循环依赖检测spring.main.allow-circular-references=false
  2. 创建自定义检测工具:在开发环境中定期检查
  3. 代码审查关注点:检查构造器注入的依赖图
  4. 使用依赖分析工具:JDepend 等

8.3 解决循环依赖的决策流程

┌──────────────────┐     是     ┌──────────────────┐
│ 是否可以重构设计?├──────────→│ 应用重构设计模式  │
└───────┬──────────┘            └──────────────────┘
        │ 否
        ▼
┌──────────────────┐     是     ┌──────────────────┐
│ 能否采用事件机制? ├──────────→│ 采用事件驱动架构  │
└───────┬──────────┘            └──────────────────┘
        │ 否
        ▼
┌──────────────────┐     是     ┌──────────────────────────────────────┐
│ 是否为构造器注入  ├──────────→│ 必须改变注入方式!                     │
│ 循环依赖?        │            │ (Spring无法解决构造器注入循环依赖      │
└───────┬──────────┘            │  因为需要先有实例才能进行依赖注入)    │
        │ 否                    └─────────────┬────────────────────────┘
        │                                     │
        │                                     ▼
        │                       ┌────────────────────────────────────┐
        │                       │ 选择以下任一方案:                   │
        │                       │ 1. 改为字段注入                     │
        │                       │ 2. 使用Provider/ObjectProvider     │
        │                       │ 3. 使用@Lazy (性能次优)            │
        │                       └────────────────────────────────────┘
        ▼
┌──────────────────┐
│ 根据具体场景选择: │
└───────┬──────────┘
        │
        ├─────────┬─────────┬─────────┬─────────┐
        │         │         │         │         │
        ▼         ▼         ▼         ▼         ▼
┌──────────┐ ┌─────────┐ ┌────────┐ ┌───────┐ ┌─────────┐
│ Provider │ │ @Lazy   │ │@Post   │ │字段   │ │自定义   │
│ 注入     │ │ 注解    │ │Const.. │ │注入   │ │处理器   │
└──────────┘ └─────────┘ └────────┘ └───────┘ └─────────┘
  (推荐)     (简单场景)   (初始化)   (简单项目) (复杂场景)

8.4 循环依赖解决方案对比

解决方案侵入性性能影响适用场景实现复杂度
重构设计所有场景
@Lazy 注解简单循环依赖
Provider需要延迟获取
事件驱动异步通信场景
@PostConstruct初始化依赖
中介者模式多对象交互
自定义处理器复杂场景

九、案例分析:微服务架构中的循环依赖解决方案

9.1 实际案例:订单与库存服务循环依赖

原始代码(存在循环依赖):

// 订单服务
@Service
public class OrderService {
    private final InventoryService inventoryService;

    @Autowired
    public OrderService(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    public Order createOrder(OrderRequest request) {
        // 检查库存
        boolean available = inventoryService.checkAvailability(
            request.getProductId(), request.getQuantity());

        if (!available) {
            throw new OutOfStockException();
        }

        Order order = new Order();
        // 设置订单信息

        // 锁定库存
        inventoryService.lockInventory(
            request.getProductId(), request.getQuantity(), order.getId());

        return order;
    }
}

// 库存服务
@Service
public class InventoryService {
    private final OrderService orderService;

    @Autowired
    public InventoryService(OrderService orderService) {
        this.orderService = orderService;
    }

    public boolean checkAvailability(Long productId, int quantity) {
        // 检查库存
        return true;
    }

    public void lockInventory(Long productId, int quantity, Long orderId) {
        // 锁定库存

        // 更新订单状态(这里导致了循环依赖)
        orderService.updateOrderStatus(orderId, "INVENTORY_LOCKED");
    }
}

9.2 重构方案:事件驱动架构

// 订单服务
@Service
public class OrderService {
    private final InventoryClient inventoryClient;
    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public OrderService(InventoryClient inventoryClient,
                        ApplicationEventPublisher eventPublisher) {
        this.inventoryClient = inventoryClient;
        this.eventPublisher = eventPublisher;
    }

    public Order createOrder(OrderRequest request) {
        // 检查库存
        boolean available = inventoryClient.checkAvailability(
            request.getProductId(), request.getQuantity());

        if (!available) {
            throw new OutOfStockException();
        }

        Order order = new Order();
        // 设置订单信息

        // 发布订单创建事件
        eventPublisher.publishEvent(
            new OrderCreatedEvent(order.getId(), request.getProductId(), request.getQuantity()));

        return order;
    }

    @EventListener
    public void handleInventoryLockedEvent(InventoryLockedEvent event) {
        // 更新订单状态
        Order order = findById(event.getOrderId());
        order.setStatus("INVENTORY_LOCKED");
        save(order);
    }
}

// 库存服务
@Service
public class InventoryService {
    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public InventoryService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @EventListener
    public void handleOrderCreatedEvent(OrderCreatedEvent event) {
        // 锁定库存
        lockInventory(event.getProductId(), event.getQuantity(), event.getOrderId());

        // 发布库存锁定事件
        eventPublisher.publishEvent(
            new InventoryLockedEvent(event.getOrderId(), event.getProductId(), event.getQuantity()));
    }

    private void lockInventory(Long productId, int quantity, Long orderId) {
        // 锁定库存逻辑
    }
}

// 事件类
@Getter
public class OrderCreatedEvent {
    private final Long orderId;
    private final Long productId;
    private final int quantity;

    public OrderCreatedEvent(Long orderId, Long productId, int quantity) {
        this.orderId = orderId;
        this.productId = productId;
        this.quantity = quantity;
    }
}

@Getter
public class InventoryLockedEvent {
    private final Long orderId;
    private final Long productId;
    private final int quantity;

    public InventoryLockedEvent(Long orderId, Long productId, int quantity) {
        this.orderId = orderId;
        this.productId = productId;
        this.quantity = quantity;
    }
}

9.3 微服务版本:使用消息队列完全解耦并处理分布式事务

// 订单服务(微服务1)
@Service
public class OrderService {
    private final InventoryClient inventoryClient;
    private final MessagePublisher messagePublisher;
    private final TransactionTemplate transactionTemplate;
    private final MessageRepository messageRepository;

    // 构造函数依赖注入
    @Autowired
    public OrderService(InventoryClient inventoryClient,
                        MessagePublisher messagePublisher,
                        PlatformTransactionManager transactionManager,
                        MessageRepository messageRepository) {
        this.inventoryClient = inventoryClient;
        this.messagePublisher = messagePublisher;
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.messageRepository = messageRepository;
    }

    public Order createOrder(OrderRequest request) {
        // 步骤1: 检查库存可用性(远程调用)
        boolean available = inventoryClient.checkAvailability(
            request.getProductId(), request.getQuantity());

        if (!available) {
            throw new OutOfStockException();
        }

        // 步骤2: 在一个本地事务中创建订单和准备消息
        return transactionTemplate.execute(status -> {
            try {
                // 步骤2.1: 创建订单记录
                Order order = new Order();
                order.setProductId(request.getProductId());
                order.setQuantity(request.getQuantity());
                order.setStatus("CREATED");
                orderRepository.save(order);

                // 步骤2.2: 创建消息记录(本地消息表模式)
                // 用于确保消息一定会被发送,即使在消息发送过程中出现故障
                TransactionMessage message = new TransactionMessage();
                message.setMessageId(UUID.randomUUID().toString());
                message.setTopic("order-created");
                message.setPayload(createOrderCreatedPayload(order, request));
                message.setStatus("PENDING"); // 标记为待发送状态
                messageRepository.save(message);

                // 步骤2.3: 注册事务同步器,在事务提交后发送消息
                // 这是Spring提供的机制,可以确保在事务成功提交后执行特定操作
                TransactionSynchronizationManager.registerSynchronization(
                    new TransactionSynchronizationAdapter() {
                        @Override
                        public void afterCommit() {
                            // 事务成功提交后立即发送消息
                            messagePublisher.publishOrderCreated(message);
                        }
                    }
                );

                return order;
            } catch (Exception e) {
                // 任何异常都会导致事务回滚
                log.error("创建订单失败", e);
                throw e;
            }
        });
    }

    // 步骤3: 消息确认处理 - 标记消息已成功发送
    @Transactional
    public void confirmMessageSent(String messageId) {
        TransactionMessage message = messageRepository.findById(messageId)
            .orElseThrow(() -> new IllegalArgumentException("Message not found"));
        message.setStatus("SENT"); // 更新消息状态为已发送
        messageRepository.save(message);
    }

    // 步骤4: 处理库存操作结果
    @KafkaListener(topics = "inventory-result")
    public void handleInventoryResult(InventoryResultMessage message) {
        // 在新事务中处理库存操作结果
        transactionTemplate.execute(status -> {
            try {
                // 查找相关订单
                Order order = orderRepository.findById(message.getOrderId())
                    .orElseThrow(() -> new IllegalArgumentException("Order not found"));

                if (message.isSuccess()) {
                    // 步骤4.1: 库存锁定成功 - 更新订单状态
                    order.setStatus("INVENTORY_LOCKED");
                } else {
                    // 步骤4.2: 库存锁定失败 - 执行补偿操作
                    order.setStatus("INVENTORY_FAILED");
                    // 可以触发其他补偿逻辑,如通知用户、释放其他资源等
                    handleCompensation(order, message.getReason());
                }

                orderRepository.save(order);
                return null;
            } catch (Exception e) {
                log.error("处理库存结果消息失败", e);
                throw e;
            }
        });
    }

    // 补偿处理逻辑
    private void handleCompensation(Order order, String reason) {
        log.info("执行订单补偿流程,订单ID: {}, 原因: {}", order.getId(), reason);
        // 实现具体的补偿逻辑
    }
}

// 库存服务(微服务2)
@Service
public class InventoryService {
    private final MessagePublisher messagePublisher;
    private final TransactionTemplate transactionTemplate;
    private final ProcessedMessageRepository processedMessageRepository;

    @Autowired
    public InventoryService(MessagePublisher messagePublisher,
                           PlatformTransactionManager transactionManager,
                           ProcessedMessageRepository processedMessageRepository) {
        this.messagePublisher = messagePublisher;
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.processedMessageRepository = processedMessageRepository;
    }

    // 处理订单创建消息
    @KafkaListener(topics = "order-created")
    public void handleOrderCreatedMessage(OrderCreatedMessage message) {
        // 步骤1: 幂等性检查 - 确保消息只被处理一次
        if (isMessageProcessed(message.getMessageId())) {
            log.info("消息已处理,跳过: {}", message.getMessageId());
            return;
        }

        // 步骤2: 在事务中处理库存锁定
        transactionTemplate.execute(status -> {
            try {
                // 步骤2.1: 获取产品库存(使用悲观锁防止并发问题)
                Inventory inventory = inventoryRepository.findByProductIdWithLock(message.getProductId());

                // 步骤2.2: 检查库存是否充足
                if (inventory.getAvailableQuantity() >= message.getQuantity()) {
                    // 步骤2.3: 扣减可用库存,增加锁定库存
                    inventory.setAvailableQuantity(inventory.getAvailableQuantity() - message.getQuantity());
                    inventory.setLockedQuantity(inventory.getLockedQuantity() + message.getQuantity());
                    inventoryRepository.save(inventory);

                    // 步骤2.4: 记录消息处理结果
                    saveMessageProcessResult(message.getMessageId(), "SUCCESS");

                    // 步骤2.5: 发送成功消息
                    messagePublisher.publishInventoryResult(
                        new InventoryResultMessage(message.getOrderId(), true, "SUCCESS"));
                } else {
                    // 步骤2.6: 库存不足 - 记录失败原因
                    saveMessageProcessResult(message.getMessageId(), "FAILED: INSUFFICIENT_INVENTORY");

                    // 步骤2.7: 发送失败消息,触发订单补偿
                    messagePublisher.publishInventoryResult(
                        new InventoryResultMessage(message.getOrderId(), false, "INSUFFICIENT_INVENTORY"));
                }

                return null;
            } catch (Exception e) {
                // 步骤2.8: 异常处理
                log.error("处理订单创建消息失败", e);
                saveMessageProcessResult(message.getMessageId(), "FAILED: " + e.getMessage());

                // 发送失败消息,触发订单补偿
                messagePublisher.publishInventoryResult(
                    new InventoryResultMessage(message.getOrderId(), false, e.getMessage()));

                throw e;
            }
        });
    }

    // 步骤3: 处理订单取消,释放库存
    @KafkaListener(topics = "order-cancelled")
    public void handleOrderCancelledMessage(OrderCancelledMessage message) {
        // 幂等性检查
        if (isMessageProcessed(message.getMessageId())) {
            return;
        }

        transactionTemplate.execute(status -> {
            try {
                // 步骤3.1: 获取库存记录
                Inventory inventory = inventoryRepository.findByProductIdWithLock(message.getProductId());

                // 步骤3.2: 释放已锁定的库存
                inventory.setLockedQuantity(inventory.getLockedQuantity() - message.getQuantity());
                inventory.setAvailableQuantity(inventory.getAvailableQuantity() + message.getQuantity());
                inventoryRepository.save(inventory);

                // 步骤3.3: 记录处理结果
                saveMessageProcessResult(message.getMessageId(), "SUCCESS");

                // 步骤3.4: 发送确认消息
                messagePublisher.publishInventoryReleased(
                    new InventoryReleasedMessage(message.getOrderId(), true));

                return null;
            } catch (Exception e) {
                log.error("处理订单取消消息失败", e);
                saveMessageProcessResult(message.getMessageId(), "FAILED: " + e.getMessage());
                throw e;
            }
        });
    }

    // 幂等性检查方法
    private boolean isMessageProcessed(String messageId) {
        return processedMessageRepository.existsById(messageId);
    }

    // 保存消息处理结果
    private void saveMessageProcessResult(String messageId, String result) {
        ProcessedMessage processedMessage = new ProcessedMessage();
        processedMessage.setMessageId(messageId);
        processedMessage.setResult(result);
        processedMessage.setProcessTime(new Date());
        processedMessageRepository.save(processedMessage);
    }
}

分布式事务处理详解:

  1. 本地消息表模式

    • 订单服务将订单和发送消息在同一事务中处理
    • 使用消息表记录所有待发送/已发送消息
    • 定时任务扫描失败消息并重试
  2. 消息幂等处理

    • 每个消息都有唯一 ID
    • 接收方记录已处理的消息 ID
    • 通过业务主键+消息 ID 防止重复处理
  3. 补偿机制

    • 每个操作都设计对应的补偿操作
    • 如:订单创建的补偿是订单取消,库存锁定的补偿是库存释放
    • 补偿操作必须是幂等的
  4. 状态追踪

    • 所有业务实体都有明确的状态迁移
    • 状态变更有完整的日志记录
    • 定时任务检查长时间处于中间状态的记录

这种基于消息驱动的架构能够完全解决循环依赖问题,同时通过最终一致性保证分布式事务的正确性。

十、总结与展望

循环依赖问题是 Spring 开发中的一个常见挑战,本文探讨了从基础的@Lazy注解到高级解决方案的多种技巧:

  1. 理解原因:Spring 的三级缓存机制能解决部分循环依赖,但构造器注入循环依赖无法自动解决
  2. 基础解决方案@Lazy注解适用于简单场景,但存在性能和可维护性问题
  3. 高级解决方案
    • Provider/ObjectProvider 提供真正的延迟加载
    • 事件驱动架构彻底解耦相互依赖的组件
    • 中介者/门面模式重构业务逻辑
    • 自定义 BeanPostProcessor 提供精细控制
  4. 实践经验:预防胜于治疗,遵循良好的设计原则减少循环依赖产生的可能性

随着微服务架构和云原生应用的普及,未来解决循环依赖的方向将更加侧重于:

  • 反应式编程模型:使用 Flux/Mono 处理异步依赖
  • 函数式架构:减少状态依赖
  • 服务网格:在基础设施层解决服务间依赖问题

通过本文提供的深入分析和实用技术,开发者可以超越简单的@Lazy注解,全面掌握 Spring Boot 中循环依赖的解决之道,构建更加健壮和可维护的应用程序。


感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!

如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码上Java.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值