1. 简介
本文聚焦于 Spring 的 State Machine 项目——它适用于表示工作流或任何有限状态自动机问题。当业务逻辑涉及复杂状态转换时,这个库能帮你避免写出面条式的 if-else 代码。
2. Maven 依赖
先添加核心依赖(最新版本可查 这里):
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
3. 状态机配置
下面定义一个简单状态机:
@Configuration
@EnableStateMachine
public class SimpleStateMachineConfiguration
extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states
.withStates()
.initial("SI")
.end("SF")
.states(
new HashSet<String>(Arrays.asList("S1", "S2", "S3")));
}
@Override
public void configure(
StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions.withExternal()
.source("SI").target("S1").event("E1").and()
.withExternal()
.source("S1").target("S2").event("E2").and()
.withExternal()
.source("S2").target("SF").event("end");
}
}
关键点:
- 需继承
StateMachineConfigurerAdapter
并实现配置方法 configure()
定义所有可能状态configure()
定义事件如何触发状态转换
这个配置创建了一个线性状态机,流程很直观:
启动状态机并使用:
@Autowired
private StateMachine<String, String> stateMachine;
// 启动状态机
stateMachine.start();
// 触发状态转换
stateMachine.sendEvent("E1");
// 检查当前状态
stateMachine.getState();
4. 动作(Actions)
给状态转换添加动作。首先定义 Action Bean:
@Bean
public Action<String, String> initAction() {
return ctx -> System.out.println(ctx.getTarget().getId());
}
在转换配置中注册:
@Override
public void configure(
StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions.withExternal()
.source("SI").target("S1")
.event("E1").action(initAction()) // 转换时执行
动作也能绑定到状态本身:
@Bean
public Action<String, String> executeAction() {
return ctx -> System.out.println("Do" + ctx.getTarget().getId());
}
states
.withStates()
.state("S3", executeAction(), errorAction()); // 正常动作 + 异常处理
异常处理动作示例:
@Bean
public Action<String, String> errorAction() {
return ctx -> System.out.println(
"Error " + ctx.getSource().getId() + ctx.getException());
}
⚠️ 状态生命周期动作:
stateEntry()
:进入状态时执行state()
:状态持续时执行stateExit()
:离开状态时执行
states
.withStates()
.stateEntry("S3", entryAction())
.state("S3", executeAction())
.stateExit("S3", exitAction());
5. 全局监听器
为状态机添加全局事件监听(适用于日志/审计):
public class StateMachineListener extends StateMachineListenerAdapter {
@Override
public void stateChanged(State from, State to) {
System.out.printf("Transitioned from %s to %s%n", from == null ?
"none" : from.getId(), to.getId());
}
}
配置监听器:
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.listener(new StateMachineListener());
}
6. 扩展状态
用扩展状态存储业务数据(如审批次数):
@Bean
public Action<String, String> executeAction() {
return ctx -> {
int approvals = (int) ctx.getExtendedState().getVariables()
.getOrDefault("approvalCount", 0);
approvals++;
ctx.getExtendedState().getVariables()
.put("approvalCount", approvals);
};
}
7. 守卫(Guards)
守卫类似动作但返回布尔值,用于状态转换前校验:
@Bean
public Guard<String, String> simpleGuard() {
return ctx -> (int) ctx.getExtendedState()
.getVariables()
.getOrDefault("approvalCount", 0) > 0;
}
使用守卫:
transitions.withExternal()
.source("S1").target("S2")
.event("E2")
.guard(simpleGuard()); // 满足条件才转换
✅ 也可用 SPeL 表达式简化:
.guardExpression("extendedState.variables.approvalCount > 0")
8. 构建器模式创建状态机
不用 Spring 注解也能创建状态机(适合测试或简单场景):
StateMachineBuilder.Builder<String, String> builder
= StateMachineBuilder.builder();
builder.configureStates().withStates()
.initial("SI")
.state("S1")
.end("SF");
builder.configureTransitions()
.withExternal()
.source("SI").target("S1").event("E1")
.and().withExternal()
.source("S1").target("SF").event("E2");
StateMachine<String, String> machine = builder.build();
9. 层级状态
通过 parent()
定义嵌套状态:
states
.withStates()
.initial("SI")
.state("SI")
.end("SF")
.and()
.withStates()
.parent("SI") // 嵌套在 SI 下
.initial("SUB1")
.state("SUB2")
.end("SUBEND");
启动后状态 ID 返回集合:
stateMachine.getState().getIds()
// ["SI", "SUB1"]
10. 选择点(Junctions)
实现条件分支转换:
- 声明选择点:
states.withStates().junction("SJ")
- 定义分支逻辑:
.withJunction() .source("SJ") .first("high", highGuard()) // 第一个守卫通过走 high .then("medium", mediumGuard()) // 第二个守卫通过走 medium .last("low") // 都不通过走 low
守卫示例:
@Bean
public Guard<String, String> mediumGuard() {
return ctx -> false; // 永不触发
}
@Bean
public Guard<String, String> highGuard() {
return ctx -> false; // 永不触发
}
⚠️ API 提供了 junctions
和 choices
,但功能完全相同
11. 分叉(Fork)
并行拆分执行路径:
states
.withStates()
.initial("SI")
.fork("SFork")
.and()
.withStates()
.parent("SFork")
.initial("Sub1-1")
.end("Sub1-2")
.and()
.withStates()
.parent("SFork")
.initial("Sub2-1")
.end("Sub2-2");
分叉转换定义:
.withFork()
.source("SFork")
.target("Sub1-1")
.target("Sub2-1");
12. 汇合(Join)
与分叉相对,等待多个子状态完成后汇合:
定义汇合点:
states.withStates().join("SJoin")
配置汇合条件:
transitions
.withJoin()
.source("Sub1-2") // 需完成的状态1
.source("Sub2-2") // 需完成的状态2
.target("SJoin"); // 目标状态
13. 使用枚举替代字符串
生产环境推荐用枚举增强类型安全:
public enum ApplicationReviewStates {
PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED
}
public enum ApplicationReviewEvents {
APPROVE, REJECT
}
配置类调整泛型参数:
public class SimpleEnumStateMachineConfiguration
extends StateMachineConfigurerAdapter
<ApplicationReviewStates, ApplicationReviewEvents>
使用枚举定义转换:
transitions.withExternal()
.source(ApplicationReviewStates.PEER_REVIEW)
.target(ApplicationReviewStates.PRINCIPAL_REVIEW)
.event(ApplicationReviewEvents.APPROVE)
14. 总结
Spring State Machine 提供了强大且灵活的状态管理能力,特别适合:
- ✅ 复杂业务流程(如审批流)
- ✅ 订单生命周期管理
- ✅ 游戏状态控制
踩坑提示:
- ❌ 状态机未启动时发送事件会被忽略
- ❌ 守卫返回 false 时不会抛异常,需通过监听器排查
- ⚠️ 层级状态中
parent()
调用顺序很重要
掌握这些核心概念后,就能轻松应对大多数状态机场景。完整代码示例可查看 官方文档。