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() 定义事件如何触发状态转换

这个配置创建了一个线性状态机,流程很直观: SI - SF

启动状态机并使用:

@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)

实现条件分支转换:

  1. 声明选择点:
    states.withStates().junction("SJ")
    
  2. 定义分支逻辑:
    .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 提供了 junctionschoices,但功能完全相同

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)

与分叉相对,等待多个子状态完成后汇合: forkjoin

定义汇合点:

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() 调用顺序很重要

掌握这些核心概念后,就能轻松应对大多数状态机场景。完整代码示例可查看 官方文档


原始标题:A Guide to the Spring State Machine Project | Baeldung