Spring StateMachine嵌套状态流转

 更新时间:2026年05月23日 09:10:56   作者:吴声子夜歌  
文章介绍了SpringStatemachine中嵌套状态的概念、配置方法及流转路由,嵌套状态用于表达“状态内的状态”,树状结构清晰,通过.parent()配置父子关系,withExternal和withLocal分别实现跨级/流转和内部切换,测试时通过连续投递事件,验证状态机状态集合展现嵌套层次

1、问题概述

在 Spring StateMachine 中,嵌套状态(Substates / Hierarchical States)是指在一个父状态(Parent State)内部,包含了一组子状态(Child States)。这种设计非常适合用来表达“状态中的状态”,能够将复杂的业务逻辑按层级进行拆分。

在嵌套状态中,状态流转遵循一个核心原则:如果状态机当前处于某个子状态,那么它必然也同时处于该子状态的父状态。

假设订单业务中有如下状态:

  • PENDING_PAY,顶层状态:待支付(初始状态)
  • IN_PAYMENT,父状态:支付中
    • PAY_PREPARE,子状态:支付准备中
    • PAY_PROCESSING,子状态:扣款中
  • PENDING_DELIVER,结束状态:待发货

2、定义状态与事件枚举

//订单状态枚举
public enum OrderState {
    PENDING_PAY,        // 顶层状态:待支付(初始状态)
    IN_PAYMENT,         // 父状态:支付中
    // 以下为 IN_PAYMENT 的内部子状态
    PAY_PREPARE,        // 子状态:支付准备中
    PAY_PROCESSING,     // 子状态:扣款中
    PENDING_DELIVER     // 结束状态:待发货
}
//订单事件枚举
public enum OrderEvent {
    GO_PAY,             // 外部事件:去支付(待支付 -> 支付中)
    BANK_CONNECT,       // 内部事件:连接渠道(支付准备中 -> 扣款中)
    PAY_SUCCESS,        // 业务结果事件:扣款成功(从扣款中跳出 -> 待发货)
    PAY_FAIL            // 业务结果事件:扣款失败(从扣款中跳出 -> 回到待支付)
}

3、配置层级状态节点

在 configure(StateMachineStateConfigurer) 中,我们利用 .parent() 语法来编织状态之间的树状层级关系。

@Configuration
@EnableStateMachineFactory(name = "paymentStateMachineFactory")
public class PaymentStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
        states
            .withStates()
                //注册最外层的顶层独立状态
                .initial(OrderState.PENDING_PAY) //顶层起点:待支付
                .end(OrderState.PENDING_DELIVER) //顶层终点:待发货
                .state(OrderState.IN_PAYMENT)    //宏观负状态:支付中
                .and()
            //嵌套子状态
            .withStates()
                .parent(OrderState.IN_PAYMENT)   //指明父节点
                .initial(OrderState.PAY_PREPARE) //子状态的第一个状态
                .state(OrderState.PAY_PROCESSING);//扣款中
    }
}

4、配置层级流转路由 rules

在配置流转时,Spring StateMachine 的嵌套路由支持两种经典的流转模式:

  • 外部流转(withExternal):导致状态发生宏观层级跨越的跳转(比如跳入/跳出父状态)。
  • 局部流转(withLocal):在父状态的树内部进行安全的子状态切换,不会触发父状态的 onExit 或 onEntry 动作。
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
    transitions
        // 1. 待支付 -> 跳入父状态(支付中)
        // 此时状态机激活集合变为:[IN_PAYMENT, PAY_PREPARE]
        .withExternal()
            .source(OrderState.PENDING_PAY).target(OrderState.IN_PAYMENT)
            .event(OrderEvent.GO_PAY)
            .and()
        // 2. 父状态内部流转:支付准备中 -> 扣款中
        // 此时状态机激活集合变为:[IN_PAYMENT, PAY_PROCESSING]
        .withExternal()
            .source(OrderState.PAY_PREPARE).target(OrderState.PAY_PROCESSING)
            .event(OrderEvent.BANK_CONNECT)
            .and()
        // 3. 【核心流转一】:扣款成功,打破父状态,流转到顶层的“待发货”
        // 状态机将彻底销毁内部的所有子状态指针
        .withExternal()
            .source(OrderState.IN_PAYMENT).target(OrderState.PENDING_DELIVER)
            .event(OrderEvent.PAY_SUCCESS)
            .and()
        // 4. 【核心流转二】:扣款失败,打破父状态,无条件滚回到顶层的“待支付”
        // 状态机同样会销毁内部指针,让订单恢复到最初可以重新发起支付的状态
        .withExternal()
            .source(OrderState.IN_PAYMENT).target(OrderState.PENDING_PAY)
            .event(OrderEvent.PAY_FAIL);
}

5、测试调用与状态打印

我们在测试中连续投递事件,看看状态机的内存激活状态集合(sm.getState().getIds())是如何展现嵌套层次的。

public void testHierarchicalStates() {
    StateMachine<OrderState, OrderEvent> sm = factory.getStateMachine("order_999");
    sm.startReactively().block();
    System.out.println("--- 1. 初始化 ---");
    System.out.println("当前激活状态: " + sm.getState().getIds()); // [PENDING_ORDER]
    // 2. 去支付 -> 激活嵌套
    sm.sendEvent(Mono.just(MessageBuilder.withPayload(OrderEvent.GO_PAY).build())).blockLast();
    System.out.println("--- 2. 发送 GO_PAY 后 ---");
    System.out.println("当前激活状态: " + sm.getState().getIds()); 
    // 🎯 预期输出: [IN_PAYMENT, PAY_PREPARE] (父子同时激活)
    // 3. 连接银行 -> 子状态流转
    sm.sendEvent(Mono.just(MessageBuilder.withPayload(OrderEvent.BANK_CONNECT).build())).blockLast();
    System.out.println("--- 3. 发送 BANK_CONNECT 后 ---");
    System.out.println("当前激活状态: " + sm.getState().getIds()); 
    // 🎯 预期输出: [IN_PAYMENT, PAY_PROCESSING] (父状态不变,子状态向前)
    // 4. 支付成功 -> 强行打破嵌套,融合成顶层终态
    sm.sendEvent(Mono.just(MessageBuilder.withPayload(OrderEvent.PAY_SUCCESS).build())).blockLast();
    System.out.println("--- 4. 发送 PAY_SUCCESS 后 ---");
    System.out.println("当前激活状态: " + sm.getState().getIds()); 
    // 🎯 预期输出: [PENDING_DELIVER] (子状态全部销毁,回归单顶层状态)
}
--- 初始状态 ---
[PENDING_PAY]
--- 1. 点击去支付后 ---
[IN_PAYMENT, PAY_PREPARE]
--- 2. 进入银行扣款中后 ---
[IN_PAYMENT, PAY_PROCESSING]
--- 3. 收到扣款失败回调后 ---
[PENDING_PAY]
--- 4. 第二次尝试,重新进入扣款中 ---
[IN_PAYMENT, PAY_PROCESSING]
--- 5. 收到扣款成功回调后 ---
[PENDING_DELIVER]

到此这篇关于Spring StateMachine嵌套状态流转的文章就介绍到这了,更多相关Spring StateMachine嵌套状态内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mongo分布式锁Java实现方法(推荐)

    mongo分布式锁Java实现方法(推荐)

    下面小编就为大家带来一篇mongo分布式锁Java实现方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • 探讨Java 将Markdown文件转换为Word和PDF文档

    探讨Java 将Markdown文件转换为Word和PDF文档

    这篇文章主要介绍了Java 将Markdown文件转换为Word和PDF文档,本文通过分步指南及代码示例展示了如何将 Markdown 文件转换为 Word 文档和 PDF 文件,需要的朋友可以参考下
    2024-07-07
  • 关于Java中Object类的几个方法示例

    关于Java中Object类的几个方法示例

    这篇文章主要给大家介绍了关于Java中Object类的几个方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-05-05
  • 基于CXF搭建webService的实例讲解

    基于CXF搭建webService的实例讲解

    下面小编就为大家带来一篇基于CXF搭建webService的实例讲解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • 教你如何轻松学会Java快慢指针法

    教你如何轻松学会Java快慢指针法

    要想把搬砖的效率提高,我们肯定是逃不掉数据结构和算法知识的,这不,可爱的小编今天就和大家一起学习来了,今天给大家分享的是快慢指针,那啥是快慢指针呢,文中有非常详细的解释,需要的朋友可以参考下
    2021-06-06
  • SpringBoot中的统一异常处理详细解析

    SpringBoot中的统一异常处理详细解析

    这篇文章主要介绍了SpringBoot中的统一异常处理详细解析,该注解可以把异常处理器应用到所有控制器,而不是单个控制器,借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,需要的朋友可以参考下
    2024-01-01
  • Java中的synchronized重量级锁解析

    Java中的synchronized重量级锁解析

    这篇文章主要介绍了Java中的synchronized重量级锁解析,内核需要去申请这个互斥量,必须要进入内核态,也就是这里需要用户态,内核态的切换,状态的切换,开销是比较大的,这就是重型锁的一个弊端,需要的朋友可以参考下
    2024-01-01
  • 自定义SpringBoot的白标错误页面的操作方法

    自定义SpringBoot的白标错误页面的操作方法

    Spring Boot的白标错误页面是在应用程序出现错误时(如404或500 HTTP状态码)自动生成的默认错误页面,下面小编给大家分享如何自定义SpringBoot的白标错误页面,感兴趣的朋友跟随小编一起看看吧
    2024-06-06
  • java中LinkedBlockingQueue与ArrayBlockingQueue的异同

    java中LinkedBlockingQueue与ArrayBlockingQueue的异同

    这篇文章主要介绍了java中LinkedBlockingQueue与ArrayBlockingQueue的异同,需要的朋友可以参考下
    2016-08-08
  • 利用SpringBoot和LiteFlow解锁复杂流程

    利用SpringBoot和LiteFlow解锁复杂流程

    随着业务的复杂化,企业需要更加高效、便捷地管理自己的业务流程,这就需要借助一些流程引擎实现,今天,我们就来介绍一种基于Java语言开发的轻量级工作流引擎——LiteFlow,以及如何在Spring Boot框架中集成它,从而提高企业的工作效率和开发效率
    2023-06-06

最新评论