Spring StateMachine 框架详解

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 本质上提供的是:

  1. 声明式规则定义— 状态和转换关系集中配置
  2. 自动校验— 未定义的转换自动拒绝
  3. 生命周期钩子— Entry/Exit/Transition Action 和 Guard
  4. 可观察性— Listener 监听所有状态变更事件
  5. 持久化支持— 状态可以保存到数据库并恢复
  6. 高级特性— 层次状态、并行区域、定时触发器

核心思想是把隐含在代码逻辑中的状态规则显式化,使其成为可配置、可审计、可测试的独立关注点。