Spring StateMachine 框架详解
一、什么是 Spring StateMachine
Spring StateMachine 是 Spring 官方提供的状态机框架,用于在 Spring 应用中以声明式的方式定义和管理有限状态机。当前最新版本为 4.0.2,基于 Spring Framework 6.x 构建。
它的核心价值是将散落在 if/else 中的状态流转逻辑集中声明、统一管理,让状态转换规则成为可配置、可审计、可测试的一等公民。
Maven 依赖:
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-starter</artifactId><version>4.0.2</version></dependency>注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、核心概念
| 概念 | 说明 | 类比 |
|---|---|---|
| State(状态) | 对象当前所处的情形 | 交通灯:红、黄、绿 |
| Event(事件) | 触发状态变化的外部输入 | 定时器到期、按钮按下 |
| Transition(转换) | 从一个状态到另一个状态的路径 | 红→绿 |
| Guard(守卫条件) | 转换前的条件校验,返回 true 才允许转换 | 库存 > 0 才能发货 |
| Action(动作) | 转换过程中执行的业务逻辑 | 发货时扣减库存 |
| Entry Action | 进入某个状态时自动执行 | 进入"已支付"状态时发短信 |
| Exit Action | 离开某个状态时自动执行 | 离开"待支付"状态时释放预占库存 |
| Region(区域) | 并行状态的容器,一个状态机可以有多个区域同时运转 | 订单同时有支付状态和物流状态 |
| Hierarchical State(层次状态) | 状态可以嵌套,子状态继承父状态的转换 | “处理中"包含"拣货”、"打包"子状态 |
三、基本使用步骤
步骤1:定义状态和事件枚举
// 状态枚举publicenumOrderState{UNPAID,// 待支付PAID,// 已支付SHIPPED,// 已发货COMPLETED,// 已完成CANCELLED// 已取消}// 事件枚举publicenumOrderEvent{PAY,// 支付SHIP,// 发货CONFIRM,// 确认收货CANCEL// 取消}步骤2:配置状态机
@Configuration@EnableStateMachinepublicclassOrderStateMachineConfigextendsEnumStateMachineConfigurerAdapter<OrderState,OrderEvent>{/** * 配置所有状态. */@Overridepublicvoidconfigure(StateMachineStateConfigurer<OrderState,OrderEvent>states)throwsException{states.withStates().initial(OrderState.UNPAID)// 初始状态.end(OrderState.COMPLETED)// 终态.end(OrderState.CANCELLED)// 终态.states(EnumSet.allOf(OrderState.class));// 所有状态}/** * 配置状态转换规则. */@Overridepublicvoidconfigure(StateMachineTransitionConfigurer<OrderState,OrderEvent>transitions)throwsException{transitions// 待支付 → 已支付(事件:PAY).withExternal().source(OrderState.UNPAID).target(OrderState.PAID).event(OrderEvent.PAY).action(payAction())// 转换时执行的动作.guard(payGuard())// 转换前的校验.and()// 已支付 → 已发货(事件:SHIP).withExternal().source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP).and()// 已发货 → 已完成(事件:CONFIRM).withExternal().source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.CONFIRM).and()// 待支付 → 已取消.withExternal().source(OrderState.UNPAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL).and()// 已支付 → 已取消.withExternal().source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);}}步骤3:定义 Guard(守卫条件)
@BeanpublicGuard<OrderState,OrderEvent>payGuard(){returncontext->{// 从上下文中获取业务数据Objectamount=context.getExtendedState().getVariables().get("amount");if(amount==null){returnfalse;}// 校验支付金额大于0return((BigDecimal)amount).compareTo(BigDecimal.ZERO)>0;};}Guard 返回true转换继续执行,返回false转换被拒绝(状态不变,不报错)。
步骤4:定义 Action(转换动作)
@BeanpublicAction<OrderState,OrderEvent>payAction(){returncontext->{// 转换过程中执行的业务逻辑IntegerorderId=(Integer)context.getExtendedState().getVariables().get("orderId");System.out.println("订单 "+orderId+" 支付成功,发送通知...");// 可以调用其他 Service};}步骤5:业务中使用状态机
@ServicepublicclassOrderService{@AutowiredprivateStateMachine<OrderState,OrderEvent>stateMachine;publicvoidpayOrder(IntegerorderId,BigDecimalamount){// 设置业务上下文数据stateMachine.getExtendedState().getVariables().put("orderId",orderId);stateMachine.getExtendedState().getVariables().put("amount",amount);// 发送事件,状态机自动完成校验和转换booleanaccepted=stateMachine.sendEvent(OrderEvent.PAY);if(accepted){// 获取转换后的状态OrderStatecurrentState=stateMachine.getState().getId();System.out.println("当前状态: "+currentState);// PAID}else{System.out.println("状态转换被拒绝");}}}四、监听器(Listener)
Spring StateMachine 支持通过监听器来观察状态机的各种行为:
@ComponentpublicclassOrderStateMachineListenerextendsStateMachineListenerAdapter<OrderState,OrderEvent>{@OverridepublicvoidstateChanged(State<OrderState,OrderEvent>from,State<OrderState,OrderEvent>to){System.out.println("状态变更: "+(from!=null?from.getId():"无")+" → "+to.getId());}@Overridepublicvoidtransition(Transition<OrderState,OrderEvent>transition){System.out.println("转换执行: "+transition.getSource().getId()+" → "+transition.getTarget().getId());}@OverridepublicvoideventNotAccepted(Message<OrderEvent>event){System.out.println("事件被拒绝: "+event.getPayload());}}也可以使用注解方式:
@WithStateMachinepublicclassOrderStateMachineHandler{@OnTransition(source="UNPAID",target="PAID")publicvoidonPay(){System.out.println("支付完成");}@OnTransition(source="PAID",target="SHIPPED")publicvoidonShip(){System.out.println("已发货");}@OnStateEntry(target="CANCELLED")publicvoidonEnterCancelled(){System.out.println("订单取消,执行退款...");}}五、状态持久化
问题:状态机实例是内存对象
默认情况下,状态机的当前状态保存在内存中。应用重启后状态丢失。对于业务系统(如订单),状态需要持久化到数据库。
方案一:StateMachinePersister(推荐)
核心思路:状态机只作为规则引擎使用,每次操作前从数据库恢复状态,操作后将新状态写回数据库。
@ComponentpublicclassOrderStateMachinePersister{@AutowiredprivateStateMachine<OrderState,OrderEvent>stateMachine;@AutowiredprivateStateMachinePersister<OrderState,OrderEvent,Integer>persister;/** * 发送事件并持久化. */publicbooleansendEvent(IntegerorderId,OrderEventevent){try{// 1. 从数据库恢复状态机到该订单的状态persister.restore(stateMachine,orderId);// 2. 发送事件booleanresult=stateMachine.sendEvent(event);// 3. 将新状态持久化回数据库if(result){persister.persist(stateMachine,orderId);}returnresult;}catch(Exceptione){thrownewRuntimeException("状态机操作失败",e);}}}自定义持久化适配器
@ComponentpublicclassOrderStateMachineContextPersistimplementsStateMachinePersist<OrderState,OrderEvent,Integer>{@AutowiredprivateOrderRepositoryorderRepository;@Overridepublicvoidwrite(StateMachineContext<OrderState,OrderEvent>context,IntegerorderId){// 将状态写入数据库Orderorder=orderRepository.findById(orderId).orElseThrow();order.setStatus(context.getState().name());orderRepository.save(order);}@OverridepublicStateMachineContext<OrderState,OrderEvent>read(IntegerorderId){// 从数据库读取状态Orderorder=orderRepository.findById(orderId).orElseThrow();OrderStatestate=OrderState.valueOf(order.getStatus());returnnewDefaultStateMachineContext<>(state,null,null,null);}}方案二:spring-statemachine-data-jpa
框架提供的 JPA 持久化模块,自动创建表存储状态机配置和运行状态:
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-data-jpa</artifactId></dependency>自动创建的表:
STATE_MACHINE:状态机实例STATE:状态定义TRANSITION:转换定义ACTION:动作定义GUARD:守卫定义
六、转换类型
Spring StateMachine 支持三种转换类型:
External(外部转换)
最常用,状态发生实际变化:
.withExternal().source(OrderState.UNPAID).target(OrderState.PAID).event(OrderEvent.PAY)Internal(内部转换)
状态不变,但执行动作(用于"在当前状态下响应某个事件但不离开"):
.withInternal().source(OrderState.UNPAID).event(OrderEvent.REMIND)// 催付提醒.action(sendRemindAction())// 发送提醒但状态不变Local(本地转换)
用于层次状态中,子状态之间的转换不触发父状态的 exit/entry:
.withLocal().source(OrderState.PROCESSING_PICK).target(OrderState.PROCESSING_PACK).event(OrderEvent.PICK_DONE)七、层次状态(Hierarchical States)
状态可以嵌套,用于表达复杂流程中的子阶段:
@Overridepublicvoidconfigure(StateMachineStateConfigurer<OrderState,OrderEvent>states)throwsException{states.withStates().initial(OrderState.UNPAID).state(OrderState.PROCESSING)// 父状态.end(OrderState.COMPLETED).and().withStates().parent(OrderState.PROCESSING)// 嵌套在 PROCESSING 下.initial(OrderState.PICKING)// 子状态初始:拣货.state(OrderState.PACKING)// 子状态:打包.end(OrderState.READY);// 子状态终态:就绪}状态图:
UNPAID → PROCESSING → COMPLETED │ ├── PICKING → PACKING → READY │ (子状态)八、并行区域(Regions)
一个对象同时有多个独立的状态维度:
states.withStates().initial(OrderState.NEW).fork(OrderState.FORK).join(OrderState.JOIN).state(OrderState.DONE).and()// 区域1:支付流程.withStates().parent(OrderState.FORK).initial(OrderState.PAY_PENDING).end(OrderState.PAY_DONE).and()// 区域2:库存流程.withStates().parent(OrderState.FORK).initial(OrderState.STOCK_PENDING).end(OrderState.STOCK_DONE);Fork/Join 用于并行区域的分叉和汇合,两个区域都到达终态后才合并继续。
九、Extended State(扩展状态)
除了有限的状态枚举外,状态机还可以携带业务数据上下文:
// 写入扩展状态stateMachine.getExtendedState().getVariables().put("orderId",12345);stateMachine.getExtendedState().getVariables().put("retryCount",0);// 在 Guard 或 Action 中读取@BeanpublicGuard<OrderState,OrderEvent>retryGuard(){returncontext->{IntegerretryCount=(Integer)context.getExtendedState().getVariables().get("retryCount");returnretryCount<3;// 重试不超过3次};}十、Spring StateMachine vs 隐式状态机
| 维度 | Spring StateMachine | 隐式状态机(if/else) |
|---|---|---|
| 规则集中度 | 所有转换规则在配置类中一目了然 | 散落在各业务方法中 |
| 非法转换防护 | 框架自动拒绝未定义的转换 | 依赖开发者手动校验 |
| 动作绑定 | Action 与转换声明式绑定 | 业务逻辑和状态修改混在一起 |
| 可视化 | 可以导出状态图(UML) | 需要人工画图 |
| 学习成本 | 需要学习框架API和概念 | 零学习成本 |
| 运行时开销 | 有框架对象创建和管理开销 | 几乎无开销 |
| 并发安全 | 框架内置处理 | 需要自行加锁 |
| 适用场景 | 状态多(>5)、转换复杂、需要审计 | 状态少(<5)、流转简单直接 |
| 持久化 | 需要额外配置持久化策略 | 天然就是数据库字段 |
| 调试 | 通过监听器追踪完整转换链路 | 打断点跟踪散落的代码 |
十一、实际项目中的选型建议
适合用 Spring StateMachine 的场景
- 状态数量多(7个以上),转换路径复杂
- 需要层次状态或并行区域
- 状态转换需要严格的审计日志
- 多人协作,需要规则集中可读
- 状态机需要动态修改(从数据库加载配置)
适合用隐式状态机的场景
- 状态少(3-5个),流转路径线性
- 转换规则稳定不常变化
- 项目已经用隐式方式写了大量代码,迁移成本高
- 性能敏感,不想引入框架开销
十二、总结
Spring StateMachine 本质上提供的是:
- 声明式规则定义— 状态和转换关系集中配置
- 自动校验— 未定义的转换自动拒绝
- 生命周期钩子— Entry/Exit/Transition Action 和 Guard
- 可观察性— Listener 监听所有状态变更事件
- 持久化支持— 状态可以保存到数据库并恢复
- 高级特性— 层次状态、并行区域、定时触发器
核心思想是把隐含在代码逻辑中的状态规则显式化,使其成为可配置、可审计、可测试的独立关注点。