当前位置: 首页 > news >正文

状态机——SpringStateMachine嵌套状态流转

SpringStateMachine嵌套状态流转

    • 1、问题概述
    • 2、定义状态与事件枚举
    • 3、配置层级状态节点
    • 4、配置层级流转路由 rules
    • 5、测试调用与状态打印

1、问题概述

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

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

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

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

2、定义状态与事件枚举

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

3、配置层级状态节点

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

@Configuration@EnableStateMachineFactory(name="paymentStateMachineFactory")publicclassPaymentStateMachineConfigextendsEnumStateMachineConfigurerAdapter<OrderState,OrderEvent>{@Overridepublicvoidconfigure(StateMachineStateConfigurer<OrderState,OrderEvent>states)throwsException{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 动作。
@Overridepublicvoidconfigure(StateMachineTransitionConfigurer<OrderState,OrderEvent>transitions)throwsException{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())是如何展现嵌套层次的。

publicvoidtestHierarchicalStates(){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]
http://www.jsqmd.com/news/860996/

相关文章:

  • Onekey:3分钟搞定Steam游戏清单的终极解决方案
  • Bandcamp音乐下载神器:高效获取高品质独立音乐的完整指南
  • 从7年开发到AI大模型工程师:我的转型踩坑与逆袭之路!附企业级项目避坑指南
  • Miro致力弥合AI潜力与组织现实之间的鸿沟
  • 如何实现远程桌面隐私模式:RustDesk隐藏操作的专业指南
  • mpv.net:Windows平台最强大的开源媒体播放器解决方案
  • CANN/asc-devkit浮点ilogbf函数文档
  • 极物科技 正点原子 RK3588 部署 Qwen2-VL Qwen2-VL-2B-Instruct,提供模型和npu驱动0.9.8的kernel
  • Android树状视图终极指南:GysoTreeView全方位解析与实战教程
  • UnattendGenerator进阶教程:如何创建复杂的多阶段安装配置
  • CANN/asc-devkit:__hltu函数文档
  • TEAMMATES测试策略详解:从单元测试到E2E测试的完整覆盖
  • 2026年5月新发布:锡林浩特近视防控实力商家深度解析与选择指南 - 2026年企业推荐榜
  • Linux 文件隐藏属性 chattr、lsattr 详解——锁住文件防误删(运维必备)
  • Orbit存储系统完全指南:SQLite、IndexedDB与Firestore三大方案深度解析
  • CANN算子数据类型列表配置
  • CANN/asc-devkit atanf函数文档
  • curtains.js实战案例:AJAX导航与平面移除的高级应用
  • 终极GTA5游戏助手:YimMenu完整实战指南
  • 中文Kodi媒体中心终极指南:4大本土化插件解决方案
  • SeekStorm入门指南:5分钟构建你的第一个高性能搜索引擎
  • ROCm rocr-libhsakmt分析系列3: aperture概念
  • 终极指南:如何彻底解决PHP Intelephense常见问题:索引失败、内存溢出、补全失效
  • 3小时重构视觉可信度:Midjourney拟物化风格紧急修复指南——含光照角度校准、微纹理叠加、物理反射模拟速查表
  • 5分钟掌握:跨平台获取官方macOS安装包的终极指南
  • FFXVIFix终极优化指南:5分钟解锁《最终幻想16》完美游戏体验
  • CMake基础:常用内部变量和环境变量的引用
  • Enumerize扩展模块:如何创建可重用的枚举定义
  • Octree-GS终极指南:如何用LOD结构化3D高斯实现实时大规模场景渲染
  • 10分钟掌握XGBoost:机器学习竞赛的终极梯度提升库