SpringStateMachine嵌套状态流转
-
- 1、问题概述
- 2、定义状态与事件枚举
- 3、配置层级状态节点
- [4、配置层级流转路由 rules](#4、配置层级流转路由 rules)
- 5、测试调用与状态打印
1、问题概述
在 Spring StateMachine 中,嵌套状态(Substates / Hierarchical States)是指在一个父状态(Parent State)内部,包含了一组子状态(Child States)。这种设计非常适合用来表达"状态中的状态",能够将复杂的业务逻辑按层级进行拆分。
在嵌套状态中,状态流转遵循一个核心原则:如果状态机当前处于某个子状态,那么它必然也同时处于该子状态的父状态。
假设订单业务中有如下状态:
- PENDING_PAY,顶层状态:待支付(初始状态)
- IN_PAYMENT ,父状态:支付中
PAY_PREPARE,子状态:支付准备中PAY_PROCESSING,子状态:扣款中
- PENDING_DELIVER,结束状态:待发货
2、定义状态与事件枚举
java
//订单状态枚举
public enum OrderState {
PENDING_PAY, // 顶层状态:待支付(初始状态)
IN_PAYMENT, // 父状态:支付中
// 以下为 IN_PAYMENT 的内部子状态
PAY_PREPARE, // 子状态:支付准备中
PAY_PROCESSING, // 子状态:扣款中
PENDING_DELIVER // 结束状态:待发货
}
java
//订单事件枚举
public enum OrderEvent {
GO_PAY, // 外部事件:去支付(待支付 -> 支付中)
BANK_CONNECT, // 内部事件:连接渠道(支付准备中 -> 扣款中)
PAY_SUCCESS, // 业务结果事件:扣款成功(从扣款中跳出 -> 待发货)
PAY_FAIL // 业务结果事件:扣款失败(从扣款中跳出 -> 回到待支付)
}
3、配置层级状态节点
在 configure(StateMachineStateConfigurer) 中,我们利用 .parent() 语法来编织状态之间的树状层级关系。
java
@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 动作。
java
@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())是如何展现嵌套层次的。
java
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]