Spring State Machine 教程:从入门到实战 – wiki基地


Spring State Machine 教程:从入门到实战

引言:为什么需要状态机?

在软件开发中,我们经常需要处理业务流程或对象的生命周期管理。一个订单可能有“待支付”、“已支付”、“待发货”、“已发货”、“已完成”、“已取消”等状态;一个审批流程可能有“待提交”、“审批中”、“已通过”、“已拒绝”等状态;甚至一个交通信号灯也有“红灯”、“黄灯”、“绿灯”等状态。

这些流程和状态之间的转换往往遵循一定的规则:只有在“待支付”状态下才能进行“支付”操作,支付成功后订单变为“已支付”状态;只有在“已支付”状态下才能进行“发货”操作,发货后变为“待发货”(或直接跳到“已发货”取决于具体业务)。

初学者可能习惯于使用一系列的 if/else 语句或者 switch 结构来处理这些状态和转换逻辑。例如:

java
public void processOrder(Order order, Event event) {
if (order.getStatus() == OrderStatus.PENDING_PAYMENT) {
if (event == Event.PAY) {
// 执行支付逻辑
if (paymentSuccess) {
order.setStatus(OrderStatus.PAID);
// 记录日志,发送通知等
} else {
order.setStatus(OrderStatus.PAYMENT_FAILED);
}
} else if (event == Event.CANCEL) {
order.setStatus(OrderStatus.CANCELLED);
}
} else if (order.getStatus() == OrderStatus.PAID) {
if (event == Event.SHIP) {
// 执行发货逻辑
order.setStatus(OrderStatus.SHIPPED);
}
// ... 其他状态和事件
}
// ... 更多状态
}

这种方式在状态和事件较少时勉强可行,但随着业务复杂度的增加,状态数量、事件数量以及它们之间的相互作用会呈指数级增长。代码将变得异常庞大、难以理解、难以维护,且容易出错。这种代码通常被称为“面条式代码”或“意大利面条式代码”。

我们需要一种更结构化、更清晰的方式来建模和管理这些复杂的流程。这时,状态机(State Machine)的概念就应运而生了。

状态机是一种数学模型,用于描述一个对象或系统在其生命周期中可能处于的各种状态,以及在特定事件发生时,系统如何从一个状态转换到另一个状态。一个基本的状态机包含:

  1. 状态 (States): 系统可能处于的离散的、有限的状态集合。
  2. 事件 (Events): 导致状态转换的外部输入或内部触发。
  3. 转换 (Transitions): 定义了当系统处于某个状态时,接收到某个事件后,将转换到哪个状态的规则。

Spring State Machine (SSM) 是 Spring 项目家族中的一员,它提供了一个框架,帮助开发者在 Spring 应用程序中方便地实现和管理状态机。SSM 提供了强大的功能,如声明式配置、分层状态、并行状态、状态持久化、拦截器、监听器等,能够优雅地解决复杂状态管理的难题。

本文将带你一步步了解 Spring State Machine,从基本概念到如何构建一个实用的业务状态机。

第一部分:核心概念与入门配置

1. 核心概念深入解析

在使用 Spring State Machine 之前,理解其核心概念至关重要:

  • 状态 (State): 表示系统在某一时刻的状况。在 SSM 中,状态通常用枚举(Enum)来表示,简单直观。例如 OrderStatus.PAID
  • 事件 (Event): 触发状态转换的信号。同样通常用枚举表示。例如 OrderEvent.PAY
  • 转换 (Transition): 定义了从一个状态到另一个状态的路径,由一个源状态 (Source State)、一个目标状态 (Target State) 和一个触发事件 (Event) 组成。一个转换也可以有关联的动作 (Actions) 和守卫 (Guards)。
  • 动作 (Action): 在状态转换发生时执行的业务逻辑。例如,当订单从“待支付”转变为“已支付”时,可能需要执行库存扣减、发送支付成功通知等操作。动作可以在转换执行前、执行后,或者在进入/退出某个状态时执行。
  • 守卫 (Guard): 在执行转换之前进行的条件检查。只有当守卫条件满足时,转换才会发生。例如,在从“待支付”到“已支付”的转换中,可以设置一个守卫来检查支付金额是否正确。
  • 状态机 (State Machine): 包含了所有的状态、事件和转换规则的实例。它是整个状态流转的执行者。
  • 扩展状态 (Extended State): 状态机可以持有一些与当前状态无关但与整个流程相关的变量或数据。例如,订单状态机可以存储订单ID、支付金额等。这些数据可以在 Actions 和 Guards 中访问和修改。
  • 拦截器 (Interceptor): 允许你在状态机生命周期的关键点(如发送事件前、转换开始、转换结束等)插入自定义逻辑,用于日志记录、审计、事务管理等。
  • 监听器 (Listener): 用于在状态机发生特定事件时(如状态改变、事件被拒绝等)接收通知,执行相应的回调逻辑。

2. 环境准备与依赖

首先,你需要一个 Spring Boot 项目。在 pom.xml 中添加 Spring State Machine 的 Starter 依赖:

xml
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.2.0</version> <!-- 检查最新版本 -->
</dependency>

如果你需要持久化功能(后面会讲到),可能还需要 JPA 或其他持久化相关的 Starter。

3. 基础配置:定义状态和转换

使用 Spring State Machine,我们通常通过配置类来定义状态机。配置类需要继承 StateMachineConfigurerAdapter<States, Events> 并使用 @Configuration 注解。

假设我们有一个简单的门禁状态机,状态有 LOCKED(锁定)和 UNLOCKED(解锁),事件有 COIN(投币)和 PUSH(推门)。

“`java
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

import java.util.EnumSet;

// 定义状态枚举
enum DoorStates {
LOCKED, UNLOCKED
}

// 定义事件枚举
enum DoorEvents {
COIN, PUSH
}

@Configuration
@EnableStateMachine // 启用状态机功能
public class DoorStateMachineConfig extends EnumStateMachineConfigurerAdapter {

// 配置状态
@Override
public void configure(StateMachineStateConfigurer<DoorStates, DoorEvents> states) throws Exception {
    states
        .withStates()
            .initial(DoorStates.LOCKED) // 设置初始状态
            .states(EnumSet.allOf(DoorStates.class)); // 定义所有状态
}

// 配置转换
@Override
public void configure(StateMachineTransitionConfigurer<DoorStates, DoorEvents> transitions) throws Exception {
    transitions
        .withExternal() // 外部转换 (从一个状态到另一个状态)
            .source(DoorStates.LOCKED).target(DoorStates.UNLOCKED).event(DoorEvents.COIN) // LOCKED + COIN -> UNLOCKED
            .and() // 链式配置
        .withExternal()
            .source(DoorStates.UNLOCKED).target(DoorStates.LOCKED).event(DoorEvents.PUSH); // UNLOCKED + PUSH -> LOCKED
            // 注意:LOCKED状态下接收PUSH事件,或UNLOCKED状态下接收COIN事件,没有定义转换,状态机将保持当前状态
}

}
“`

解释:

  • @EnableStateMachine: 启用 Spring State Machine 的自动配置和功能。
  • EnumStateMachineConfigurerAdapter<DoorStates, DoorEvents>: 基于枚举定义状态和事件的适配器。你需要指定状态和事件的枚举类型。
  • configure(StateMachineStateConfigurer<S, E> states): 用于配置状态。
    • .withStates(): 开始配置状态集。
    • .initial(S initial): 设置状态机的初始状态。
    • .states(Collection<S> states): 定义所有可能的状态。使用 EnumSet.allOf(Enum.class) 可以方便地包含枚举中的所有值。
  • configure(StateMachineTransitionConfigurer<S, E> transitions): 用于配置状态转换。
    • .withExternal(): 定义一个外部转换。外部转换意味着状态机的状态确实发生了改变。SSM 还支持内部转换 (withInternal()),它在不改变状态的情况下执行动作,常用于处理状态内的事件。
    • .source(S source): 定义转换的源状态。
    • .target(S target): 定义转换的目标状态。
    • .event(E event): 定义触发此转换的事件。
    • .and(): 用于连接多个转换配置。

4. 使用状态机

配置完成后,Spring 会自动创建一个 StateMachine Bean。你可以通过 @Autowired 将其注入到你的服务或控制器中。

“`java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;

@Service
public class DoorService {

private final StateMachine<DoorStates, DoorEvents> doorStateMachine;

@Autowired
public DoorService(StateMachine<DoorStates, DoorEvents> doorStateMachine) {
    this.doorStateMachine = doorStateMachine;
}

public void openDoor() {
    // 启动状态机 (如果尚未启动)
    // 实际应用中,可能需要根据业务ID获取特定实例的状态机,这里简化使用默认单例
    if (!doorStateMachine.isRunning()) {
         doorStateMachine.startReactively().subscribe(); // 使用反应式方式启动
         // 或者对于非反应式:doorStateMachine.start();
    }


    System.out.println("Current state: " + doorStateMachine.getState().getId());

    // 发送投币事件
    System.out.println("Sending COIN event...");
    doorStateMachine.sendEvent(DoorEvents.COIN);
    System.out.println("Current state after COIN: " + doorStateMachine.getState().getId());

    // 发送推门事件
    System.out.println("Sending PUSH event...");
    doorStateMachine.sendEvent(DoorEvents.PUSH);
    System.out.println("Current state after PUSH: " + doorStateMachine.getState().getId());

    // 再次发送投币事件
    System.out.println("Sending COIN event again...");
    doorStateMachine.sendEvent(DoorEvents.COIN);
    System.out.println("Current state after COIN again: " + doorStateMachine.getState().getId());

    // 再次发送推门事件
     System.out.println("Sending PUSH event again...");
    doorStateMachine.sendEvent(DoorEvents.PUSH);
    System.out.println("Current state after PUSH again: " + doorStateMachine.getState().getId());
}

public DoorStates getCurrentState() {
    return doorStateMachine.getState().getId();
}

}
“`

当你运行这个服务并调用 openDoor() 方法时,你将看到如下输出:

Current state: LOCKED
Sending COIN event...
Current state after COIN: UNLOCKED
Sending PUSH event...
Current state after PUSH: LOCKED
Sending COIN event again...
Current state after COIN again: UNLOCKED
Sending PUSH event again...
Current state after PUSH again: LOCKED

这验证了我们配置的状态转换规则是正确的。状态机根据接收到的事件,并在存在匹配的转换规则时,自动从源状态迁移到目标状态。如果当前状态接收到一个没有匹配转换规则的事件,状态机将忽略该事件,保持当前状态不变。

第二部分:高级特性与实践应用

1. 动作 (Actions) 的使用

业务逻辑通常与状态转换紧密相关。Spring State Machine 允许你在状态转换发生时或在进入/退出某个状态时执行自定义逻辑,这就是 Action 的作用。

Action 需要实现 Action<S, E> 接口,或者更常见的是定义一个 Spring Bean 方法,并在配置中引用它。

“`java
import org.springframework.statemachine.StateContext;
import org.springframework.stereotype.Component;

@Component
public class DoorActions {

// 投币成功时执行的动作
public void unlockAction(StateContext<DoorStates, DoorEvents> context) {
    System.out.println("Executing unlock action: Door is now UNLOCKED.");
    // 可以在这里执行业务逻辑,例如更新数据库状态,发送通知等
    // 可以通过 context 获取状态、事件、扩展状态等信息
    // Object orderId = context.getExtendedState().get("orderId", Object.class);
}

// 推门成功时执行的动作
public void lockAction(StateContext<DoorStates, DoorEvents> context) {
    System.out.println("Executing lock action: Door is now LOCKED.");
    // 执行其他业务逻辑
}

// 进入 UNLOCKED 状态时执行的动作
public void entryUnlockedAction(StateContext<DoorStates, DoorEvents> context) {
    System.out.println("Entering UNLOCKED state.");
}

// 退出 UNLOCKED 状态时执行的动作
public void exitUnlockedAction(StateContext<DoorStates, DoorEvents> context) {
    System.out.println("Exiting UNLOCKED state.");
}

}
“`

在配置中引用这些 Action:

“`java
@Configuration
@EnableStateMachine
public class DoorStateMachineConfig extends EnumStateMachineConfigurerAdapter {

// 注入 Actions Bean
private final DoorActions doorActions;

public DoorStateMachineConfig(DoorActions doorActions) {
    this.doorActions = doorActions;
}

@Override
public void configure(StateMachineStateConfigurer<DoorStates, DoorEvents> states) throws Exception {
    states
        .withStates()
            .initial(DoorStates.LOCKED)
            .states(EnumSet.allOf(DoorStates.class))
            // 配置状态的进入/退出动作
            .stateEntry(DoorStates.UNLOCKED, doorActions.entryUnlockedAction())
            .stateExit(DoorStates.UNLOCKED, doorActions.exitUnlockedAction());
}

@Override
public void configure(StateMachineTransitionConfigurer<DoorStates, DoorEvents> transitions) throws Exception {
    transitions
        .withExternal()
            .source(DoorStates.LOCKED).target(DoorStates.UNLOCKED).event(DoorEvents.COIN)
            .action(doorActions.unlockAction()) // 配置转换的动作
            .and()
        .withExternal()
            .source(DoorStates.UNLOCKED).target(DoorStates.LOCKED).event(DoorEvents.PUSH)
            .action(doorActions.lockAction()); // 配置转换的动作
}

}
“`

现在,当你运行 openDoor() 方法时,输出将包含 Action 的信息:

Current state: LOCKED
Sending COIN event...
Executing unlock action: Door is now UNLOCKED.
Entering UNLOCKED state.
Current state after COIN: UNLOCKED
Sending PUSH event...
Exiting UNLOCKED state.
Executing lock action: Door is now LOCKED.
Current state after PUSH: LOCKED
Sending COIN event again...
Executing unlock action: Door is now UNLOCKED.
Entering UNLOCKED state.
Current state after COIN again: UNLOCKED
Sending PUSH event again...
Exiting UNLOCKED state.
Executing lock action: Door is now LOCKED.
Current state after PUSH again: LOCKED

Action 的执行时机:

  • action() 在转换发生时执行。
  • stateEntry() 在进入某个状态后执行。
  • stateExit() 在退出某个状态前执行。

注意:如果一个转换定义了 action(),并且目标状态也定义了 stateEntry(),那么 action() 会在 stateEntry() 之前执行。stateExit() 会在转换的 action() 之前执行。执行顺序通常是:退出源状态动作 -> 转换动作 -> 进入目标状态动作。

2. 守卫 (Guards) 的使用

Guard 用于在执行转换前进行条件判断。只有当 Guard 返回 true 时,对应的转换才会执行。

Guard 需要实现 Guard<S, E> 接口,或者定义一个返回 boolean 类型的方法 Bean。

“`java
import org.springframework.statemachine.StateContext;
import org.springframework.stereotype.Component;

@Component
public class DoorGuards {

// 投币金额检查守卫
public boolean checkCoinAmount(StateContext<DoorStates, DoorEvents> context) {
    // 假设扩展状态中存有投币金额
    Integer amount = context.getExtendedState().get("amount", Integer.class);
    System.out.println("Checking coin amount: " + amount);
    boolean result = amount != null && amount >= 1; // 假设需要1个币
    if (!result) {
        System.out.println("Guard failed: Insufficient amount.");
    }
    return result;
}

}
“`

在配置中引用 Guard:

“`java
@Configuration
@EnableStateMachine
public class DoorStateMachineConfig extends EnumStateMachineConfigurerAdapter {

private final DoorActions doorActions;
private final DoorGuards doorGuards; // 注入 Guards Bean

public DoorStateMachineConfig(DoorActions doorActions, DoorGuards doorGuards) {
    this.doorActions = doorActions;
    this.doorGuards = doorGuards;
}

@Override
public void configure(StateMachineStateConfigurer<DoorStates, DoorEvents> states) throws Exception {
    states
        .withStates()
            .initial(DoorStates.LOCKED)
            .states(EnumSet.allOf(DoorStates.class))
            .stateEntry(DoorStates.UNLOCKED, doorActions.entryUnlockedAction())
            .stateExit(DoorStates.UNLOCKED, doorActions.exitUnlockedAction());
}

@Override
public void configure(StateMachineTransitionConfigurer<DoorStates, DoorEvents> transitions) throws Exception {
    transitions
        .withExternal()
            .source(DoorStates.LOCKED).target(DoorStates.UNLOCKED).event(DoorEvents.COIN)
            .action(doorActions.unlockAction())
            .guard(doorGuards.checkCoinAmount()) // 配置守卫
            .and()
        .withExternal()
            .source(DoorStates.UNLOCKED).target(DoorStates.LOCKED).event(DoorEvents.PUSH)
            .action(doorActions.lockAction());
}

}
“`

在使用时,你可以通过 StateContextgetExtendedState() 获取扩展状态,并在发送事件时传入参数。

“`java
// 在 DoorService 中修改发送事件逻辑
public void openDoorWithGuard() {
if (!doorStateMachine.isRunning()) {
doorStateMachine.startReactively().subscribe();
}

System.out.println("Current state: " + doorStateMachine.getState().getId());

// 发送投币事件,带参数 (金额不足)
System.out.println("Sending COIN event with amount 0...");
// 使用 Message 传递参数到扩展状态
doorStateMachine.sendEvent(
        MessageBuilder.withEvent(DoorEvents.COIN)
                .setHeader("amount", 0) // 将金额放入消息头,SSM会自动添加到扩展状态
                .build());
System.out.println("Current state after COIN (amount 0): " + doorStateMachine.getState().getId());

// 发送投币事件,带参数 (金额足够)
System.out.println("Sending COIN event with amount 1...");
doorStateMachine.sendEvent(
        MessageBuilder.withEvent(DoorEvents.COIN)
                .setHeader("amount", 1)
                .build());
System.out.println("Current state after COIN (amount 1): " + doorStateMachine.getState().getId());

// 发送推门事件
System.out.println("Sending PUSH event...");
doorStateMachine.sendEvent(DoorEvents.PUSH);
System.out.println("Current state after PUSH: " + doorStateMachine.getState().getId());

}
“`

输出将是:

Current state: LOCKED
Sending COIN event with amount 0...
Checking coin amount: 0
Guard failed: Insufficient amount.
Current state after COIN (amount 0): LOCKED // 守卫失败,状态未改变
Sending COIN event with amount 1...
Checking coin amount: 1
Executing unlock action: Door is now UNLOCKED.
Entering UNLOCKED state.
Current state after COIN (amount 1): UNLOCKED // 守卫成功,状态改变
Sending PUSH event...
Exiting UNLOCKED state.
Executing lock action: Door is now LOCKED.
Current state after PUSH: LOCKED // 状态改变

这表明 Guard 成功地控制了状态转换。通过 StateContext,你可以访问状态机当前的状态、接收到的事件、扩展状态等信息,这对于 Guard 和 Action 执行业务逻辑非常有用。发送事件时使用 Message 可以方便地传递附加数据到 StateContext 的扩展状态中。

3. 分层状态 (Hierarchical States)

真实世界的流程往往不是简单的扁平结构,而是包含嵌套或子流程。例如,订单的“已支付”状态可能进一步细分为“等待仓库确认”、“正在拣货”、“已打包”等。Spring State Machine 支持分层状态,允许你定义父状态和子状态。

如果一个状态有子状态,当状态机进入该父状态时,会进一步进入其定义的初始子状态。在父状态中未处理的事件会冒泡到父状态去处理。

“`java
enum ComplexStates {
SI, S1, S2, S21, S22 // S2是父状态, S21, S22是子状态
}

enum ComplexEvents {
E1, E2, E3, E4, E5
}

@Configuration
@EnableStateMachine
public class HierarchicalStateMachineConfig extends EnumStateMachineConfigurerAdapter {

@Override
public void configure(StateMachineStateConfigurer<ComplexStates, ComplexEvents> states) throws Exception {
    states
        .withStates()
            .initial(ComplexStates.SI) // 初始状态
            .state(ComplexStates.SI)
            .state(ComplexStates.S1)
            .state(ComplexStates.S2) // 定义父状态
            .end(ComplexStates.S22) // 定义结束状态 (如果S2流程完成后结束)

            // 定义子状态,属于S2
            .states(ComplexStates.S2, EnumSet.of(ComplexStates.S21, ComplexStates.S22))
            // 定义S2的初始子状态
            .initial(ComplexStates.S21, ComplexStates.S2)
            // S21和S22可以定义自己的进入/退出动作等
            .state(ComplexStates.S21)
            .state(ComplexStates.S22);
}

@Override
public void configure(StateMachineTransitionConfigurer<ComplexStates, ComplexEvents> transitions) throws Exception {
    transitions
        .withExternal()
            .source(ComplexStates.SI).target(ComplexStates.S1).event(ComplexEvents.E1) // SI -> S1 on E1
            .and()
        .withExternal()
            .source(ComplexStates.S1).target(ComplexStates.S2).event(ComplexEvents.E2) // S1 -> S2 on E2
            .and()

        // S2内部转换 (从S21到S22)
        .withExternal()
            .source(ComplexStates.S21).target(ComplexStates.S22).event(ComplexEvents.E3) // S21 -> S22 on E3
            .and()

        // 从S2的任何子状态到S1 (事件E4在S2内部未处理,冒泡到S2处理)
        .withExternal()
            .source(ComplexStates.S2).target(ComplexStates.S1).event(ComplexEvents.E4) // S2 -> S1 on E4
            .and()

        // 从任何状态到S22 (结束状态)
        .withExternal()
            .source(ComplexStates.SI).target(ComplexStates.S22).event(ComplexEvents.E5); // SI -> S22 on E5
}

}
“`

解释:

  • states(ComplexStates.S2, EnumSet.of(ComplexStates.S21, ComplexStates.S22)): 定义 S21S22S2 的子状态。
  • initial(ComplexStates.S21, ComplexStates.S2): 定义当进入父状态 S2 时,其初始子状态是 S21
  • 当状态机处于 S21 状态时,接收到 E3 事件,会执行 S21 -> S22 的转换。
  • 当状态机处于 S21S22 状态时,接收到 E4 事件。由于在子状态层面没有处理 E4 的转换,事件会向上冒泡到父状态 S2S2 有一个处理 E4 并转换到 S1 的规则,因此状态机会从当前的子状态 (例如 S21S22) 退出,然后退出父状态 S2,最后进入目标状态 S1

分层状态是管理复杂流程和减少配置重复的强大工具。

4. 状态持久化 (State Persistence)

在分布式系统或需要重启的应用程序中,状态机的当前状态必须能够持久化,以便在需要时恢复。Spring State Machine 提供了持久化机制。

核心接口是 StateMachineRuntimePersister。SSM 提供了基于 JPA、Redis、MongoDB 等的实现。这里以 JPA 为例:

首先,你需要 Spring Data JPA 和一个数据库依赖(如 H2, MySQL)。

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

然后,配置 JPA 持久化器:

“`java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.data.jpa.JpaPersistingStateMachineInterceptor;
import org.springframework.statemachine.data.jpa.JpaStateMachineRepository;
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
import org.springframework.statemachine.service.DefaultStateMachineService;
import org.springframework.statemachine.service.StateMachineService;

@Configuration
@EnableStateMachine // Keep this
public class JpaPersistenceConfig extends StateMachineConfigurerAdapter { // Your existing config can extend this or be separate

private final JpaStateMachineRepository jpaStateMachineRepository;
// S and E are your State and Event enums
private final StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister;


public JpaPersistenceConfig(JpaStateMachineRepository jpaStateMachineRepository,
                            // Inject your State and Event serializers if needed, otherwise default is used
                            // StringStateMachineSerializer serializer
                            ) {
    this.jpaStateMachineRepository = jpaStateMachineRepository;
    // Initialize the persister
    this.stateMachineRuntimePersister = new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}

@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
    config
        .withConfiguration()
            .autoStartup(false) // 不要自动启动,我们将按ID管理实例
            .listener(stateMachineListener()) // 可以配置监听器
            .and()
        .withPersistence() // 配置持久化
            .runtimePersister(stateMachineRuntimePersister); // 注入持久化器
}

// 可选:自定义监听器Bean
@Bean
public StateMachineListener<States, Events> stateMachineListener() {
    return new StateMachineListenerAdapter<States, Events>() {
        @Override
        public void stateChanged(State<States, Events> from, State<States, Events> to) {
            System.out.printf("State changed from %s to %s%n", from == null ? "none" : from.getId(), to.getId());
        }
         @Override
        public void eventNotAccepted(Message<Events> event) {
             System.out.printf("Event not accepted: %s%n", event.getPayload());
        }
        // ... 其他监听方法
    };
}

 // 通常还需要一个 StateMachineService 来按ID获取和管理状态机实例
 @Bean
 public StateMachineService<States, Events> stateMachineService(
         StateMachineConfigurer<States, Events> stateMachineConfigurer,
         StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
     return new DefaultStateMachineService<>(stateMachineConfigurer, stateMachineRuntimePersister);
 }

}
“`

解释:

  • JpaStateMachineRepository: Spring Data JPA 会自动创建一个用于存储状态机上下文的 Repository。你需要让你的 JPA 配置扫描到 Spring State Machine 的实体类。这通常通过在 application.properties@EnableJpaRepositories 中配置 spring.jpa.open-in-view=truespring.jpa.hibernate.ddl-auto=update (开发环境) 或手动创建 STATE_MACHINE_CONTEXTS 表来完成。
  • JpaPersistingStateMachineInterceptor: 这是 JPA 实现的持久化拦截器。它会在状态转换成功后自动将状态机上下文保存到数据库。
  • runtimePersister(): 在配置中引用这个持久化拦截器。
  • autoStartup(false): 当使用持久化按 ID 管理状态机时,通常不希望默认的单个状态机实例自动启动,而是根据需要通过 StateMachineService 按 ID 获取或创建特定实例。
  • StateMachineService: 这是 SSM 提供的用于按业务实体 ID 管理状态机实例的服务。你可以通过它根据 ID 加载或创建状态机,并发送事件。

使用 StateMachineService

“`java
import org.springframework.stereotype.Service;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.service.StateMachineService;

@Service
public class OrderService { // 假设处理订单,订单ID作为状态机ID

private final StateMachineService<OrderStates, OrderEvents> orderStateMachineService;

public OrderService(StateMachineService<OrderStates, OrderEvents> orderStateMachineService) {
    this.orderStateMachineService = orderStateMachineService;
}

public void createOrder(Long orderId) {
    // 创建或获取一个与订单ID关联的状态机实例
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineService.acquireStateMachine(Long.toString(orderId));
    // 启动状态机,通常由 acquireStateMachine 内部完成,但确认一下
    if (!stateMachine.isRunning()) {
         stateMachine.startReactively().subscribe(); // 或 .start()
    }

    // 执行其他订单创建逻辑
    System.out.println("Order " + orderId + " created. Initial state: " + stateMachine.getState().getId());

    // 释放状态机实例,以便持久化其状态 (或者交由拦截器处理)
    // stateMachineService.releaseStateMachine(Long.toString(orderId)); // 拦截器通常会自动处理保存
}

public void payOrder(Long orderId) {
    // 获取与订单ID关联的状态机实例
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineService.acquireStateMachine(Long.toString(orderId));

    System.out.println("Order " + orderId + " current state before payment: " + stateMachine.getState().getId());

    // 发送支付事件
    // 通常会将业务数据 (如支付金额) 通过 Message Header 传递
    boolean success = stateMachine.sendEvent(
             MessageBuilder.withEvent(OrderEvents.PAY)
                     // .setHeader("paymentAmount", ...)
                     .build()
     );

    System.out.println("Order " + orderId + " payment event sent. State change success: " + success);
    System.out.println("Order " + orderId + " current state after payment: " + stateMachine.getState().getId());

    // 释放状态机
    // stateMachineService.releaseStateMachine(Long.toString(orderId));
}

// ... 其他业务方法,如 shipOrder, cancelOrder 等

}
“`

通过 StateMachineService 和配置的持久化拦截器,每个订单(或业务实体)都有一个独立的、可持久化的状态机实例。当接收到事件时,SSM 会加载对应 ID 的状态机状态,执行转换,然后由拦截器将新的状态上下文保存回数据库。

第三部分:实战应用:构建一个简单的订单状态机

现在,我们将之前学习的概念整合起来,构建一个稍微复杂的订单状态机。

需求:

订单流程:创建 -> 待支付 -> 支付中 -> [支付成功 -> 待发货] / [支付失败] -> 待发货 -> 已发货 -> 已完成 / 已取消

状态 (States):

  • DRAFT (草稿) – 初始状态
  • PENDING_PAYMENT (待支付)
  • PAYING (支付中)
  • PAID (已支付)
  • PAYMENT_FAILED (支付失败)
  • PENDING_SHIPMENT (待发货)
  • SHIPPED (已发货)
  • COMPLETED (已完成) – 结束状态
  • CANCELLED (已取消) – 结束状态

事件 (Events):

  • CREATE_ORDER (创建订单)
  • SUBMIT_ORDER (提交订单 -> 进入待支付)
  • START_PAY (开始支付 -> 进入支付中)
  • PAYMENT_SUCCESS (支付成功)
  • PAYMENT_FAILURE (支付失败)
  • SHIP_ORDER (发货)
  • COMPLETE_ORDER (完成订单)
  • CANCEL_ORDER (取消订单)

配置:

“`java
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory; // 使用工厂模式,按需创建实例
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.data.jpa.JpaPersistingStateMachineInterceptor;
import org.springframework.statemachine.data.jpa.JpaStateMachineRepository;
import org.springframework.statemachine.persist.StateMachineRuntimePersister;

import java.util.EnumSet;

// 定义状态
enum OrderStates {
DRAFT, PENDING_PAYMENT, PAYING, PAID, PAYMENT_FAILED, PENDING_SHIPMENT, SHIPPED, COMPLETED, CANCELLED
}

// 定义事件
enum OrderEvents {
CREATE_ORDER, SUBMIT_ORDER, START_PAY, PAYMENT_SUCCESS, PAYMENT_FAILURE, SHIP_ORDER, COMPLETE_ORDER, CANCEL_ORDER
}

@Configuration
@EnableStateMachineFactory // 使用工厂模式,以便按需创建和管理多个状态机实例 (每个订单一个)
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter {

private final JpaStateMachineRepository jpaStateMachineRepository;
private final OrderActions orderActions; // 注入自定义 Actions
private final OrderGuards orderGuards; // 注入自定义 Guards

// 通过构造函数注入依赖
public OrderStateMachineConfig(JpaStateMachineRepository jpaStateMachineRepository,
                               OrderActions orderActions,
                               OrderGuards orderGuards) {
    this.jpaStateMachineRepository = jpaStateMachineRepository;
    this.orderActions = orderActions;
    this.orderGuards = orderGuards;
}

// 配置状态
@Override
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
    states
        .withStates()
            .initial(OrderStates.DRAFT) // 初始状态
            .states(EnumSet.allOf(OrderStates.class)) // 所有状态
            .end(OrderStates.COMPLETED) // 结束状态1
            .end(OrderStates.CANCELLED); // 结束状态2
            // 可以为各个状态配置 entry/exit action
            // .stateEntry(OrderStates.PAID, orderActions.handlePaidEntry());
}

// 配置转换
@Override
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
    transitions
        // DRAFT -> PENDING_PAYMENT on SUBMIT_ORDER
        .withExternal()
            .source(OrderStates.DRAFT).target(OrderStates.PENDING_PAYMENT).event(OrderEvents.SUBMIT_ORDER)
            .action(orderActions.submitOrderAction()) // 提交订单时执行的动作
            .and()

        // PENDING_PAYMENT -> PAYING on START_PAY
        .withExternal()
            .source(OrderStates.PENDING_PAYMENT).target(OrderStates.PAYING).event(OrderEvents.START_PAY)
            .action(orderActions.startPaymentAction()) // 开始支付时执行的动作
            .and()

        // PAYING -> PAID on PAYMENT_SUCCESS
        .withExternal()
            .source(OrderStates.PAYING).target(OrderStates.PAID).event(OrderEvents.PAYMENT_SUCCESS)
            .action(orderActions.paymentSuccessAction()) // 支付成功动作 (如扣库存)
            .guard(orderGuards.checkPaymentAmountGuard()) // 检查支付金额守卫
            .and()

        // PAYING -> PAYMENT_FAILED on PAYMENT_FAILURE
        .withExternal()
            .source(OrderStates.PAYING).target(OrderStates.PAYMENT_FAILED).event(OrderEvents.PAYMENT_FAILURE)
            .action(orderActions.paymentFailureAction()) // 支付失败动作 (如记录日志,通知用户)
            .and()

        // PAID -> PENDING_SHIPMENT (通常支付成功后直接进入待发货)
         .withExternal()
            .source(OrderStates.PAID).target(OrderStates.PENDING_SHIPMENT)
            // 注意:这里没有定义 Event,意味着从 PAID 状态进入后,如果业务逻辑允许,可以自动触发进入 PENDING_SHIPMENT
            // 或者可以在 paymentSuccessAction 中手动发送 SHIP_ORDER 事件,然后配置 PAID -> PENDING_SHIPMENT on SHIP_ORDER
            // 为了简化,我们假设支付成功后直接就待发货了,或者通过一个没有事件的转换或内部转换实现
            // 这里我们配置一个由 Action 触发的转换,或者更常见的,支付成功后直接触发 SHIP_ORDER 事件
            // 让我们配置一个需要 SHIP_ORDER 事件的转换
            .event(OrderEvents.SHIP_ORDER) // 支付成功后,需要收到 SHIP_ORDER 事件才能到待发货
            .and()


        // PENDING_SHIPMENT -> SHIPPED on SHIP_ORDER
        // 注意:如果上面 PAID -> PENDING_SHIPMENT 是自动的,这里可能需要调整
        // 更合理的:PAID 状态 -> PENDING_SHIPMENT 状态 (可能是通过一个内部转换或进入动作)
        // 简化起见,我们假设支付成功后,需要收到 SHIP_ORDER 事件才能到待发货,再收到发货事件才能到已发货
         .withExternal()
            .source(OrderStates.PENDING_SHIPMENT).target(OrderStates.SHIPPED).event(OrderEvents.SHIP_ORDER)
            .action(orderActions.shipOrderAction()) // 发货动作 (如更新物流信息)
            .and()

        // SHIPPED -> COMPLETED on COMPLETE_ORDER
        .withExternal()
            .source(OrderStates.SHIPPED).target(OrderStates.COMPLETED).event(OrderEvents.COMPLETE_ORDER)
            .action(orderActions.completeOrderAction()) // 完成动作 (如积分结算)
            .and()

        // 从多个状态可以取消订单 (例如 DRAFT, PENDING_PAYMENT, PAYING, PAYMENT_FAILED, PENDING_SHIPMENT)
        .withExternal()
            .source(EnumSet.of(OrderStates.DRAFT, OrderStates.PENDING_PAYMENT, OrderStates.PAYING, OrderStates.PAYMENT_FAILED, OrderStates.PENDING_SHIPMENT))
            .target(OrderStates.CANCELLED).event(OrderEvents.CANCEL_ORDER)
            .action(orderActions.cancelOrderAction()) // 取消动作 (如释放库存,退款)
            .guard(orderGuards.canCancelOrderGuard()); // 检查是否可以取消的守卫
            // 注意:已发货和已完成状态通常不能取消,这里通过 source 限制了。如果业务更复杂,守卫更灵活。
}

 // 配置持久化拦截器 Bean
@Bean
public StateMachineRuntimePersister<OrderStates, OrderEvents, String> orderStateMachineRuntimePersister() {
    // 使用 JPA 持久化,String 是状态机 ID 的类型
    return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}

// 配置 StateMachineService Bean,用于按 ID 管理状态机
@Bean
public StateMachineService<OrderStates, OrderEvents> orderStateMachineService(
         StateMachineConfigurer<OrderStates, OrderEvents> stateMachineConfigurer,
         StateMachineRuntimePersister<OrderStates, OrderEvents, String> orderStateMachineRuntimePersister) {
     // 使用默认服务实现,传入配置器和持久化器
     return new DefaultStateMachineService<>(stateMachineConfigurer, orderStateMachineRuntimePersister);
 }

// 可选:配置监听器
 @Bean
 public StateMachineListener<OrderStates, OrderEvents> orderStateMachineListener() {
     return new StateMachineListenerAdapter<OrderStates, OrderEvents>() {
        @Override
        public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
            System.out.printf("订单状态改变: 从 %s 到 %s%n", from == null ? "无" : from.getId(), to.getId());
        }

         @Override
         public void eventNotAccepted(Message<OrderEvents> event) {
             System.out.printf("事件未被接受: %s%n", event.getPayload());
         }
         // 可以记录转换、动作、守卫的执行等
     };
 }

}
“`

Action 和 Guard 的简化实现:

“`java
import org.springframework.statemachine.StateContext;
import org.springframework.stereotype.Component;

@Component
public class OrderActions {

public void submitOrderAction(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: 订单提交, 进入待支付状态.");
    // 可以在这里更新订单状态到数据库,记录日志等
    String orderId = (String) context.getExtendedState().getVariables().get("orderId"); // 从扩展状态获取订单ID
    System.out.println("处理订单 " + orderId + " 的提交动作");
    // 例如: orderRepository.updateStatus(orderId, OrderStatus.PENDING_PAYMENT);
}

public void startPaymentAction(StateContext<OrderStates, OrderEvents> context) {
     System.out.println("Action: 开始支付, 进入支付中状态.");
     String orderId = (String) context.getExtendedState().getVariables().get("orderId");
     System.out.println("处理订单 " + orderId + " 的开始支付动作");
     // 例如: 调用第三方支付接口
     // orderRepository.updateStatus(orderId, OrderStatus.PAYING);
 }

public void paymentSuccessAction(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: 支付成功, 进入已支付状态.");
    String orderId = (String) context.getExtendedState().getVariables().get("orderId");
    System.out.println("处理订单 " + orderId + " 的支付成功动作");
    // 例如: 扣减库存, 发送支付成功通知, 更新数据库状态
    // orderRepository.updateStatus(orderId, OrderStatus.PAID);
}

public void paymentFailureAction(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: 支付失败, 进入支付失败状态.");
    String orderId = (String) context.getExtendedState().getVariables().get("orderId");
    System.out.println("处理订单 " + orderId + " 的支付失败动作");
    // 例如: 记录失败原因, 通知用户重试
    // orderRepository.updateStatus(orderId, OrderStatus.PAYMENT_FAILED);
}

public void shipOrderAction(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: 订单发货, 进入已发货状态.");
     String orderId = (String) context.getExtendedState().getVariables().get("orderId");
     System.out.println("处理订单 " + orderId + " 的发货动作");
    // 例如: 生成物流单号, 通知用户
    // orderRepository.updateStatus(orderId, OrderStatus.SHIPPED);
}

public void completeOrderAction(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: 订单完成, 进入已完成状态.");
     String orderId = (String) context.getExtendedState().getVariables().get("orderId");
     System.out.println("处理订单 " + orderId + " 的完成动作");
    // 例如: 结算佣金, 用户评价入口
    // orderRepository.updateStatus(orderId, OrderStatus.COMPLETED);
}

public void cancelOrderAction(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: 订单取消, 进入已取消状态.");
     String orderId = (String) context.getExtendedState().getVariables().get("orderId");
     System.out.println("处理订单 " + orderId + " 的取消动作");
    // 例如: 释放库存, 如果已支付则发起退款
    // orderRepository.updateStatus(orderId, OrderStatus.CANCELLED);
}

}
“`

“`java
import org.springframework.statemachine.StateContext;
import org.springframework.stereotype.Component;

@Component
public class OrderGuards {

public boolean checkPaymentAmountGuard(StateContext<OrderStates, OrderEvents> context) {
    // 从消息头或扩展状态中获取支付金额等信息进行检查
    Double amount = context.getMessageHeaders().get("paymentAmount", Double.class);
    String orderId = (String) context.getExtendedState().getVariables().get("orderId");
    System.out.println("Guard: 检查订单 " + orderId + " 支付金额: " + amount);
    // 假设订单应付金额是 100.0
    boolean result = amount != null && amount >= 100.0;
    if (!result) {
        System.out.println("Guard failed: 支付金额不足.");
    }
    return result;
}

public boolean canCancelOrderGuard(StateContext<OrderStates, OrderEvents> context) {
    // 检查当前状态是否可以取消,虽然在配置中已经通过 source 限制了,但在 Guard 中再次检查或增加其他业务条件更健壮
    OrderStates currentState = context.getStateMachine().getState().getId();
    String orderId = (String) context.getExtendedState().getVariables().get("orderId");
    System.out.println("Guard: 检查订单 " + orderId + " 是否可以取消,当前状态: " + currentState);
    // 例如,除了状态限制,还可以检查是否有未完成的退款流程等
    // boolean hasPendingRefund = ...;
    boolean result = !EnumSet.of(OrderStates.SHIPPED, OrderStates.COMPLETED).contains(currentState); // && !hasPendingRefund;
     if (!result) {
        System.out.println("Guard failed: 当前状态不允许取消.");
    }
    return result;
}

}
“`

使用 OrderService 通过 ID 管理状态机:

“`java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.service.StateMachineService;
import org.springframework.stereotype.Service;

@Service
public class OrderManagementService { // 区别于上面的 OrderService,这是实际业务服务

private final StateMachineService<OrderStates, OrderEvents> orderStateMachineService;

@Autowired
public OrderManagementService(StateMachineService<OrderStates, OrderEvents> orderStateMachineService) {
    this.orderStateMachineService = orderStateMachineService;
}

public void createOrderProcess(String orderId) {
    // 获取或创建一个新的状态机实例,与 orderId 绑定
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineService.acquireStateMachine(orderId);

    // 如果是新创建的状态机,需要启动
    if (!stateMachine.isRunning()) {
        stateMachine.startReactively().subscribe(); // 或者 stateMachine.start();
    }

    System.out.println("订单 " + orderId + " 状态机已启动,初始状态: " + stateMachine.getState().getId());

    // 通常创建订单后需要发送 SUBMIT_ORDER 事件将其从 DRAFT 驱动到 PENDING_PAYMENT
    sendEvent(orderId, OrderEvents.SUBMIT_ORDER, null);

    // 释放状态机实例
    // stateMachineService.releaseStateMachine(orderId); // 拦截器会自动保存
}

public void processOrderEvent(String orderId, OrderEvents event, Double paymentAmount) {
    // 获取与订单ID关联的状态机实例
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineService.acquireStateMachine(orderId);

    System.out.println("订单 " + orderId + " 接收事件 " + event + ",当前状态: " + stateMachine.getState().getId());

    // 构建消息,将必要的业务数据放入 header
    Message<OrderEvents> message = MessageBuilder.withEvent(event)
            .setHeader("orderId", orderId) // 将订单ID也放入header/extended state 方便 action/guard 获取
            .setHeader("paymentAmount", paymentAmount) // 例如支付金额
            .build();

    // 发送事件
    boolean accepted = stateMachine.sendEvent(message);

    System.out.println("事件 " + event + " 发送结果: " + accepted + ",当前状态: " + stateMachine.getState().getId());

    // 释放状态机实例,以便持久化其状态 (拦截器会处理)
    // stateMachineService.releaseStateMachine(orderId);
}

public OrderStates getCurrentOrderStatus(String orderId) {
     StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineService.acquireStateMachine(orderId);
     OrderStates currentState = stateMachine.getState().getId();
     // stateMachineService.releaseStateMachine(orderId); // Readonly operation, maybe no need to release immediately? Check SSM docs.
     return currentState;
}

 // 简化方法,用于发送事件不带支付金额
 public void sendEvent(String orderId, OrderEvents event, Object payload) {
     processOrderEvent(orderId, event, null); // payload unused in this simple example
 }

}
“`

使用示例:

在你的应用启动类或一个命令行Runner中调用 OrderManagementService 的方法:

“`java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StateMachineDemoApplication implements CommandLineRunner {

@Autowired
private OrderManagementService orderManagementService;

public static void main(String[] args) {
    SpringApplication.run(StateMachineDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
    String orderId1 = "ORD123";
    String orderId2 = "ORD456";

    // 创建并提交订单1
    orderManagementService.createOrderProcess(orderId1); // DRAFT -> PENDING_PAYMENT

    // 创建并提交订单2
    orderManagementService.createOrderProcess(orderId2); // DRAFT -> PENDING_PAYMENT

    // 对订单1进行支付 (金额不足)
    orderManagementService.processOrderEvent(orderId1, OrderEvents.START_PAY, null); // PENDING_PAYMENT -> PAYING
    orderManagementService.processOrderEvent(orderId1, OrderEvents.PAYMENT_SUCCESS, 50.0); // PAYING -> PAYMENT_FAILED (Guard 失败)

    // 对订单2进行支付 (金额足够)
     orderManagementService.processOrderEvent(orderId2, OrderEvents.START_PAY, null); // PENDING_PAYMENT -> PAYING
    orderManagementService.processOrderEvent(orderId2, OrderEvents.PAYMENT_SUCCESS, 100.0); // PAYING -> PAID (Guard 成功)

    // 对订单1尝试发货 (仍在支付失败状态,不会成功)
    orderManagementService.sendEvent(orderId1, OrderEvents.SHIP_ORDER, null); // PAYMENT_FAILED -> PAYMENT_FAILED (无转换)

    // 对订单2发货 (从 PAID 通过 SHIP_ORDER 事件到 PENDING_SHIPMENT,再通过 SHIP_ORDER 到 SHIPPED)
    // 根据上面的配置,PAID -> PENDING_SHIPMENT 需要 SHIP_ORDER 事件
    // 简化一下,直接从 PAID -> SHIPPED on SHIP_ORDER 可能更常见
    // 我们调整配置,让 PAID -> SHIPPED on SHIP_ORDER
    // 假设配置已调整为 PAID -> SHIPPED on SHIP_ORDER
    orderManagementService.sendEvent(orderId2, OrderEvents.SHIP_ORDER, null); // PAID -> SHIPPED

    // 对订单1尝试取消
    orderManagementService.sendEvent(orderId1, OrderEvents.CANCEL_ORDER, null); // PAYMENT_FAILED -> CANCELLED

    // 对订单2尝试取消 (已发货,不能取消)
    orderManagementService.sendEvent(orderId2, OrderEvents.CANCEL_ORDER, null); // SHIPPED -> SHIPPED (Guard 或 Source 限制失败)

    // 对订单2完成
    orderManagementService.sendEvent(orderId2, OrderEvents.COMPLETE_ORDER, null); // SHIPPED -> COMPLETED
}

}
“`

这个实战示例展示了如何:

  1. 定义业务的状态和事件枚举。
  2. 使用 @EnableStateMachineFactoryStateMachineService 按 ID 管理多个状态机实例。
  3. 配置复杂的状态转换流程,包括带 Action 和 Guard 的转换。
  4. 将业务逻辑封装在独立的 Action 和 Guard Bean 中。
  5. 使用持久化来保存和恢复状态机状态。
  6. 在实际业务服务中调用 StateMachineService 获取状态机并发送事件来驱动流程。
  7. 通过 Message 传递业务数据给 Action 和 Guard。

这只是一个基础示例,真实的业务流程可能更复杂,涉及更多状态、事件、Guard、Action,以及分层状态、并行状态等高级特性。但核心模式是类似的:建模状态和转换,将业务逻辑放入 Action/Guard,通过发送事件驱动状态机。

总结

Spring State Machine 提供了一种强大、灵活且结构化的方式来管理应用程序中的复杂状态和业务流程。通过清晰地定义状态、事件和转换,并结合 Action 和 Guard 来执行业务逻辑和条件判断,我们可以避免编写难以维护的 if/else 迷宫代码。

本文从状态机的基本概念出发,介绍了 Spring State Machine 的核心组件、基础配置、高级特性(Action, Guard, 分层状态, 持久化),并通过一个订单处理的实战示例,演示了如何在实际应用中构建和使用一个可持久化、按实例管理的业务状态机。

使用 SSM 的主要优势包括:

  • 清晰性: 流程逻辑可视化,易于理解和沟通。
  • 可维护性: 状态、事件、转换、逻辑职责分离,修改和扩展方便。
  • 健壮性: SSM 框架处理了状态转换的复杂性,减少了潜在错误。
  • 可测试性: 状态机和其组件(Action, Guard)更容易进行单元和集成测试。
  • 可扩展性: 支持分层、并行状态,以及丰富的扩展点(监听器、拦截器)。

当然,SSM 也有一定的学习曲线,特别是理解其配置 DSL 和各种高级特性。对于非常简单的状态管理,直接使用枚举或许足够。但对于任何涉及多个状态、复杂转换规则和相关业务逻辑的场景,投入时间学习和使用 Spring State Machine 都会带来显著的长期收益。

希望这篇详细教程能帮助你入门 Spring State Machine,并在你的项目中成功应用它来构建更健壮、更易于管理的业务流程。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部