Spring State Machine:从零开始学习状态机 – wiki基地


从零开始:深入理解 Spring State Machine

在软件开发中,我们经常需要处理对象在其生命周期中的状态变化。一个订单可以从“待支付”变为“已支付”,再到“已发货”;一个用户账户可以从“未激活”变为“已激活”,再到“已锁定”;一个审批流程可能会经历“草稿”、“待审批”、“审批通过”、“审批驳回”等多个环节。这些状态变化往往不是随意的,而是遵循特定的规则和顺序,并且通常由特定的事件触发。

直接使用大量的 if/elseswitch 语句来管理这些复杂的状态转换逻辑,会使得代码变得难以理解、维护和扩展,容易形成所谓的“面条代码”(Spaghetti Code)。当状态和事件的数量增加时,这种方式的复杂度会呈指数级增长。

有没有一种更结构化、更清晰的方式来处理这种复杂的状态流转呢?答案就是 状态机(State Machine)。而对于 Java 和 Spring 生态的开发者来说,Spring State Machine (SSM) 提供了一个强大且易于使用的框架来构建和管理这些状态机。

本文将带你从零开始,一步步深入理解状态机概念,并详细学习如何在 Spring 应用中构建和使用 Spring State Machine。

第一部分:理解状态机(State Machine)的基础概念

在深入 Spring State Machine 之前,我们首先需要理解什么是状态机。

1.1 什么是状态机?

状态机(State Machine),全称有限状态机(Finite State Machine,FSM),是一种数学计算模型,用于描述一个对象在其生命周期中可能存在的有限个状态,以及在特定条件下,如何从一个状态转换到另一个状态。

一个基本的状态机包含以下几个核心元素:

  • 状态(State): 对象在某一时刻所处的特定情况或模式。状态的数量是有限的。
  • 事件(Event): 触发状态转换的外部或内部刺激。事件是导致状态变化的驱动力。
  • 转换(Transition): 在特定事件的触发下,从一个状态转移到另一个状态的过程。转换定义了状态之间允许的路径。
  • 初始状态(Initial State): 状态机启动时的第一个状态。
  • 终止状态(End State / Final State): 表示状态机流程结束的状态(可选)。

想象一下一个简单的电灯开关:

  • 状态: 开 (On)关 (Off)
  • 事件: 按下开关 (PressSwitch)
  • 转换:
    • 当状态是 时,接收到 按下开关 事件,转换到 状态。
    • 当状态是 时,接收到 按下开关 事件,转换到 状态。
  • 初始状态: 假设默认是

这就是一个最简单的状态机模型。

1.2 为什么需要状态机?

在软件开发中应用状态机模式有诸多好处:

  • 清晰的结构: 将复杂的状态流转逻辑可视化和模型化,使得系统行为一目了然。
  • 易于维护: 状态和转换逻辑被集中管理,修改或添加新的状态和事件变得更容易,降低了维护成本。
  • 减少错误: 强制规定了合法的状态转换路径,避免了非法或意外的状态跳跃。
  • 更好的可测试性: 状态机的确定性使得测试更加容易和全面。
  • 分离关注点: 将业务逻辑(状态转换规则)与具体操作(转换发生时执行的代码)分离。

考虑一个电商订单的状态管理,它可能经历:待支付 -> 已支付 -> 待发货 -> 已发货 -> 已签收。同时,在任何“待支付”或“已支付”状态都可能因为用户操作或超时而变为已取消;在“已发货”后可能因为用户退货而变为退款中,最后变成已退款等。如果用 if/else 来写,很快就会变得非常混乱。使用状态机则可以清晰地定义这些状态、事件以及它们之间的合法转换路径。

第二部分:认识 Spring State Machine (SSM)

Spring State Machine 是 Spring 家族中的一个子项目,它提供了一个框架,用于简化在 Spring 应用中状态机的开发。SSM 基于 Spring 的核心概念(如依赖注入、配置等),使得将状态机集成到现有的 Spring 项目中变得非常自然。

2.1 SSM 的核心组件与概念

SSM 提供了一系列抽象和实现来构建状态机:

  • State 代表状态机的某个状态。在 SSM 中,状态通常用枚举(Enum)或字符串表示。
  • Event 代表触发状态转换的事件。事件也通常用枚举或字符串表示。
  • Transition 定义从一个源状态(Source State)到目标状态(Target State)的转换,由特定事件触发。
  • StateMachine SSM 的核心接口,代表一个运行时的状态机实例。你可以通过它发送事件、获取当前状态等。
  • StateMachineConfiguration 用于定义状态机的结构,包括所有状态、初始状态、终止状态以及所有转换规则。通常使用 JavaConfig 方式进行配置。
  • Action 在状态转换发生时执行的业务逻辑代码。Action 可以关联到转换(transition.action(...))或者状态的进入/退出(state.entry(...), state.exit(...))。它们通常是 Spring Bean。
  • Guard 一个布尔条件的判断器,在转换发生前执行。如果 Guard 返回 false,则该转换不会发生。Guard 也通常是 Spring Bean。
  • StateMachineListener 用于监听状态机的各种事件,如状态变化、转换发生、错误发生等,可以在这些事件发生时执行一些副作用操作或日志记录。

2.2 SSM 的优势

  • 基于 Spring: 充分利用 Spring 的 IoC 和 DI 特性,Action 和 Guard 可以轻松注入其他 Bean。
  • 声明式配置: 通过 JavaConfig 或 XML(虽然 JavaConfig 更推荐)清晰地定义状态机结构,代码可读性高。
  • 强大的功能: 支持层次化状态机、并行状态机、状态持久化、拦截器、监听器等高级特性。
  • 易于集成: 可以轻松地与 Spring Integration, Spring Batch 等其他 Spring 项目集成。

第三部分:从零开始构建第一个 Spring State Machine

我们将通过一个具体的例子来学习如何使用 Spring State Machine:一个简化的订单状态机。

订单状态:
* NEW (新建)
* PAID (已支付)
* SHIPPED (已发货)
* DELIVERED (已送达)
* CANCELLED (已取消)

订单事件:
* PAY (支付)
* SHIP (发货)
* DELIVER (送达)
* CANCEL (取消)

合法的状态转换:
* NEW -> PAID (通过 PAY 事件)
* NEW -> CANCELLED (通过 CANCEL 事件)
* PAID -> SHIPPED (通过 SHIP 事件)
* PAID -> CANCELLED (通过 CANCEL 事件)
* SHIPPED -> DELIVERED (通过 DELIVER 事件)
* SHIPPED -> CANCELLED (通过 CANCEL 事件)

3.1 添加 SSM 依赖

首先,在你的 Spring Boot 项目中添加 Spring State Machine 的 Starter 依赖。如果你使用的是 Maven:

xml
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>${spring-statemachine.version}</version>
</dependency>

如果你使用的是 Gradle:

gradle
implementation 'org.springframework.statemachine:spring-statemachine-core:${springStateMachinVersion}'

请将 ${spring-statemachine.version} 替换为合适的版本号。通常,你可以查看 Spring Boot 版本对应的 Spring Cloud 或 Spring 项目版本兼容性列表,或者直接使用最新的稳定版本。

3.2 定义状态和事件

使用 Java 枚举来定义状态和事件是 SSM 推荐的方式,这提供了类型安全。

java
// 定义订单状态
public enum OrderStates {
NEW, // 新建
PAID, // 已支付
SHIPPED, // 已发货
DELIVERED, // 已送达
CANCELLED // 已取消
}

java
// 定义订单事件
public enum OrderEvents {
PAY, // 支付
SHIP, // 发货
DELIVER, // 送达
CANCEL // 取消
}

3.3 配置状态机

使用 JavaConfig 来定义状态机的结构。创建一个配置类,并使用 @EnableStateMachine 注解启用状态机功能。

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

import java.util.EnumSet;

import static com.example.demo.OrderEvents.; // 假设你的枚举包路径
import static com.example.demo.OrderStates.
; // 假设你的枚举包路径

@Configuration
@EnableStateMachine // 启用状态机功能
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter {

// 配置状态
@Override
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
    states
        .withStates()
            .initial(NEW) // 定义初始状态
            .states(EnumSet.allOf(OrderStates.class)); // 定义所有可能的状态
            // .end(DELIVERED) // 可以定义一个或多个终止状态
}

// 配置转换
@Override
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
    transitions
        // NEW -> PAID by PAY event
        .withExternal()
            .source(NEW)
            .target(PAID)
            .event(PAY)
            .and() // .and() 用于连接下一个转换配置
        // NEW -> CANCELLED by CANCEL event
        .withExternal()
            .source(NEW)
            .target(CANCELLED)
            .event(CANCEL)
            .and()
        // PAID -> SHIPPED by SHIP event
        .withExternal()
            .source(PAID)
            .target(SHIPPED)
            .event(SHIP)
            .and()
        // PAID -> CANCELLED by CANCEL event
        .withExternal()
            .source(PAID)
            .target(CANCELLED)
            .event(CANCEL)
            .and()
        // SHIPPED -> DELIVERED by DELIVER event
        .withExternal()
            .source(SHIPPED)
            .target(DELIVERED)
            .event(DELIVER)
            .and()
        // SHIPPED -> CANCELLED by CANCEL event
        .withExternal()
            .source(SHIPPED)
            .target(CANCELLED)
            .event(CANCEL);
}

// 可以选择性地配置监听器、拦截器等,稍后介绍
// @Override
// public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
//     config
//         .withConfiguration()
//             .autoStartup(true) // 配置状态机是否在启动时自动启动
//             .listener(listener()); // 添加监听器
// }

// 定义一个简单的监听器 Bean
// @Bean
// public StateMachineListener<OrderStates, OrderEvents> listener() {
//     return new StateMachineListenerAdapter<OrderStates, OrderEvents>() {
//         @Override
//         public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
//             System.out.printf("State changed from %s to %s%n", from == null ? "null" : from.getId(), to.getId());
//         }
//     };
// }

}
“`

配置详解:

  • @EnableStateMachine: 告诉 Spring Boot 启用状态机支持,并会查找 StateMachineConfigurerAdapter 的实现类来构建状态机 Bean。
  • StateMachineConfigurerAdapter<OrderStates, OrderEvents>: 这是配置状态机的核心适配器类,泛型参数分别是状态枚举和事件枚举。
  • configure(StateMachineStateConfigurer<S, E> states): 配置状态机的所有状态。
    • .withStates(): 开始状态配置。
    • .initial(NEW): 设置 NEW 为初始状态。
    • .states(EnumSet.allOf(OrderStates.class)): 添加 OrderStates 枚举中的所有状态。你也可以逐个添加状态:.state(STATE1).state(STATE2)...
    • .end(...): 可选,定义一个或多个终止状态。进入终止状态后,状态机通常会停止。
  • configure(StateMachineTransitionConfigurer<S, E> transitions): 配置状态机的所有转换。
    • .withExternal(): 配置一个外部转换(External Transition),意味着从一个状态转换到另一个状态,不涉及子状态。SSM 还支持内部转换(Internal Transition)和本地转换(Local Transition),用于更复杂的场景。
    • .source(STATE_A).target(STATE_B).event(EVENT_X): 定义一个转换:当状态机处于 STATE_A 状态,接收到 EVENT_X 事件时,转换到 STATE_B 状态。
    • .and(): 用于分隔不同的转换配置。
  • configure(StateMachineConfigurationConfigurer<S, E> config): 可选的配置方法,用于配置状态机的一些全局属性,如是否自动启动(autoStartup)、添加监听器(listener)等。autoStartup(true) 表示在 Spring ApplicationContext 加载完成后自动启动状态机。

3.4 使用状态机

配置完成后,Spring 会在 ApplicationContext 中创建一个 StateMachine Bean。你可以在任何 Spring 管理的组件中注入并使用它。

“`java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.statemachine.StateMachine;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

import static com.example.demo.OrderEvents.;
import static com.example.demo.OrderStates.
;

@Component
public class OrderProcessor implements CommandLineRunner {

@Autowired
private StateMachine<OrderStates, OrderEvents> orderStateMachine;

@Override
public void run(String... args) throws Exception {
    System.out.println("Current state: " + orderStateMachine.getState().getId());

    // 启动状态机 (如果autoStartup=false)
    // orderStateMachine.startReactively().block(); // Spring 5 响应式方式

    // 发送支付事件
    System.out.println("Sending PAY event...");
    orderStateMachine.sendEvent(PAY);
    System.out.println("Current state: " + orderStateMachine.getState().getId());

    // 发送发货事件
    System.out.println("Sending SHIP event...");
    orderStateMachine.sendEvent(SHIP);
    System.out.println("Current state: " + orderStateMachine.getState().getId());

    // 发送送达事件
    System.out.println("Sending DELIVER event...");
    orderStateMachine.sendEvent(DELIVER);
    System.out.println("Current state: " + orderStateMachine.getState().getId());

    // 尝试发送一个非法的事件 (从 DELIVERED 发送 PAY)
    System.out.println("Attempting to send PAY event from DELIVERED state...");
    orderStateMachine.sendEvent(PAY); // 这个事件不会触发任何转换
    System.out.println("Current state: " + orderStateMachine.getState().getId());

    // 停止状态机 (如果不再需要)
    // orderStateMachine.stopReactively().block(); // Spring 5 响应式方式
}

}
“`

在 Spring Boot 应用启动后,OrderProcessor 会被执行。@Autowired 注入的状态机实例会被使用。run 方法中展示了如何:

  1. 获取当前状态:orderStateMachine.getState().getId()
  2. 发送事件:orderStateMachine.sendEvent(EVENT_ENUM)。你也可以发送一个 Message 对象,其中包含事件以及可选的头部信息,头部信息可以用于 Guard 或 Action 中获取上下文数据。例如:
    java
    Message<OrderEvents> message = MessageBuilder
    .withPayload(PAY)
    .setHeader("orderId", "12345")
    .build();
    orderStateMachine.sendEvent(message);

    这种方式在需要传递额外信息时非常有用。

运行这个例子,你会看到状态按照定义的转换路径变化:NEW -> PAID -> SHIPPED -> DELIVERED。尝试从 DELIVERED 状态发送 PAY 事件时,由于配置中没有从 DELIVERED 状态通过 PAY 事件进行的转换,状态将保持不变,不会发生任何错误(除非你配置了错误处理器)。

3.5 添加 Action 和 Guard

在实际应用中,状态转换不仅仅是状态标签的变化,通常伴随着一些业务逻辑的执行。ActionGuard 正是为了解决这个问题。

Action (动作): 在转换发生时执行一段代码。Action 可以是 org.springframework.statemachine.action.Action 接口的实现,或者是一个带有特定签名的方法。通常,我们将其定义为 Spring Bean。

“`java
// 支付成功后执行的 Action
@Component // 声明为 Spring Bean
public class PaymentSuccessAction implements Action {

// 你可以注入其他服务,例如订单服务、库存服务等
// @Autowired
// private OrderService orderService;

@Override
public void execute(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Action: Payment successful! Order ID: " + context.getMessageHeaders().get("orderId"));
    // 在这里可以调用订单服务更新数据库状态、发送邮件等
    // String orderId = (String) context.getMessageHeaders().get("orderId");
    // orderService.updateOrderStatus(orderId, OrderStates.PAID);
}

}

// 取消订单时执行的 Action
@Component
public class CancelOrderAction implements Action {

@Override
public void execute(StateContext<OrderStates, OrderEvents> context) {
     System.out.println("Action: Order cancelled! Order ID: " + context.getMessageHeaders().get("orderId"));
     // 例如,释放库存
}

}
“`

在配置中引用这些 Action:

“`java
// 在 OrderStateMachineConfig 中修改 configure(StateMachineTransitionConfigurer) 方法
@Override
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
transitions
// NEW -> PAID by PAY event, execute paymentSuccessAction
.withExternal()
.source(NEW)
.target(PAID)
.event(PAY)
.action(paymentSuccessAction()) // 引用 Action Bean
.and()
// NEW -> CANCELLED by CANCEL event, execute cancelOrderAction
.withExternal()
.source(NEW)
.target(CANCELLED)
.event(CANCEL)
.action(cancelOrderAction()) // 引用 Action Bean
.and()
// … 其他转换 …
// PAID -> CANCELLED by CANCEL event, execute cancelOrderAction
.withExternal()
.source(PAID)
.target(CANCELLED)
.event(CANCEL)
.action(cancelOrderAction()); // 引用同一个 Action Bean
// .and()…

}

// 将 Action 定义为 Bean
@Bean
public PaymentSuccessAction paymentSuccessAction() {
return new PaymentSuccessAction();
}

@Bean
public CancelOrderAction cancelOrderAction() {
return new CancelOrderAction();
}
“`

StateContext 对象在 execute 方法中提供上下文信息,包括当前状态机、源状态、目标状态、事件、消息头部等,非常有用。

Guard (守卫): 在转换发生前进行条件判断。Guard 可以是 org.springframework.statemachine.guard.Guard 接口的实现,或带有特定签名的方法。如果 Guard 返回 false,则转换不会发生。通常也定义为 Spring Bean。

“`java
// 检查是否有足够的库存才能发货的 Guard
@Component // 声明为 Spring Bean
public class EnoughInventoryGuard implements Guard {

// 你可以注入库存服务
// @Autowired
// private InventoryService inventoryService;

@Override
public boolean evaluate(StateContext<OrderStates, OrderEvents> context) {
    System.out.println("Guard: Checking inventory...");
    // 假设从消息头部获取订单包含的商品ID和数量,然后调用库存服务检查
    // String orderId = (String) context.getMessageHeaders().get("orderId");
    // boolean hasEnough = inventoryService.checkInventory(orderId); // 实际检查库存
    boolean hasEnough = true; // 示例中简化为总是返回 true
    System.out.println("Guard: Inventory check result: " + hasEnough);
    return hasEnough; // 返回 true 允许转换,返回 false 阻止转换
}

}
“`

在配置中引用 Guard:

“`java
// 在 OrderStateMachineConfig 中修改 configure(StateMachineTransitionConfigurer) 方法
@Override
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
transitions
// PAID -> SHIPPED by SHIP event, guarded by enoughInventoryGuard
.withExternal()
.source(PAID)
.target(SHIPPED)
.event(SHIP)
.guard(enoughInventoryGuard()) // 引用 Guard Bean
.and()
// … 其他转换 …
}

// 将 Guard 定义为 Bean
@Bean
public EnoughInventoryGuard enoughInventoryGuard() {
return new EnoughInventoryGuard();
}
“`

现在,当从 PAID 状态通过 SHIP 事件转换到 SHIPPED 状态时,会先执行 EnoughInventoryGuardevaluate 方法。如果返回 true,转换和相关的 Action 才会执行;如果返回 false,转换被阻止,状态机保持在 PAID 状态。

3.6 状态的进入/退出 Action

除了转换上的 Action,你还可以在状态的进入(Entry)或退出(Exit)时定义 Action。

“`java
// 在 OrderStateMachineConfig 中修改 configure(StateMachineStateConfigurer) 方法
@Override
public void configure(StateMachineStateConfigurer states) throws Exception {
states
.withStates()
.initial(NEW)
.state(NEW, entryActionForNew(), exitActionForNew()) // 在NEW状态进入/退出时执行Action
.state(PAID, entryActionForPaid()) // 在PAID状态进入时执行Action
.state(SHIPPED)
.state(DELIVERED)
.state(CANCELLED); // 终止状态通常不需要退出Action
}

// 定义状态进入/退出 Action Beans
@Bean
public Action entryActionForNew() {
return context -> System.out.println(“Entry Action: Entering NEW state.”);
}

@Bean
public Action exitActionForNew() {
return context -> System.out.println(“Exit Action: Exiting NEW state.”);
}

@Bean
public Action entryActionForPaid() {
return context -> System.out.println(“Entry Action: Entering PAID state. Prepare for shipping.”);
}
“`

这样,当状态机进入 NEW 状态时会执行 entryActionForNew,离开 NEW 状态时会执行 exitActionForNew,进入 PAID 状态时会执行 entryActionForPaid

转换 Action 和状态进入/退出 Action 的区别在于触发时机:
* 转换 Action 在转换 发生时 执行。
* 状态进入 Action 在 进入 某个状态后执行。
* 状态退出 Action 在 离开 某个状态前执行。

在一个从 S1 到 S2 的转换中,执行顺序通常是:S1 的退出 Action -> 转换的 Guard (如果存在) -> 转换的 Action -> S2 的进入 Action。

第四部分:进阶概念

Spring State Machine 提供了更多高级功能来处理复杂场景。

4.1 层次化状态机 (Hierarchical State Machines)

状态可以包含子状态,形成层次结构。这有助于管理具有内部复杂性的状态。例如,一个 SHIPPED 状态可以有子状态如 PACKING, IN_TRANSIT, OUT_FOR_DELIVERY。当状态机处于 SHIPPED 状态时,它实际上会处于其某个子状态中。从父状态进行的转换会作用于所有子状态。

配置层次化状态机:

“`java
@Configuration
@EnableStateMachine
public class HierarchicalStateMachineConfig extends StateMachineConfigurerAdapter {

@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
    states
        .withStates()
            .initial("ROOT") // 根状态
            .state("ROOT")
                .state("A") // ROOT 的子状态 A
                .state("B") // ROOT 的子状态 B
                .and() // 回到 ROOT 层
            .withStates() // 定义另一个父状态
                .parent("ROOT") // 指定父状态
                .initial("A") // 指定 ROOT 的初始子状态为 A
                .state("A1") // A 的子状态 A1
                .state("A2"); // A 的子状态 A2
}

@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
    transitions
        // 从子状态 A1 通过 EVENT1 到另一个子状态 A2 (内部转换 within A)
        .withExternal()
            .source("A1")
            .target("A2")
            .event("EVENT1")
            .and()
        // 从子状态 A1 通过 EVENT2 到父状态 B (退出 A 和 ROOT 层,进入 B)
        .withExternal()
            .source("A1")
            .target("B")
            .event("EVENT2")
            .and()
        // 从父状态 ROOT 通过 EVENT3 到 ROOT 自身,但指定进入子状态 B (退出当前子状态,进入 B)
         .withExternal()
            .source("ROOT")
            .target("B")
            .event("EVENT3")
             .and()
        // 从父状态 ROOT 通过 EVENT4 到 ROOT 自身,但指定进入子状态 A 的子状态 A1 (退出当前子状态,进入 A1)
         .withExternal()
            .source("ROOT")
            .target("A1") // 注意可以直接指定子状态作为 target
            .event("EVENT4");
}

}
“`
层次化状态机极大地提高了复杂状态模型的组织性和复用性。

4.2 状态持久化 (State Persistence)

对于长生命周期的状态机(如订单、流程审批),需要在应用重启或机器故障后恢复其状态。SSM 提供了状态持久化机制。你可以将状态机的当前状态、历史状态以及其他上下文信息存储到数据库(JDBC, JPA)、缓存(Redis)或其他存储介质中。

SSM 提供了 StateMachinePersister 接口及其各种实现(如 JpaStateMachinePersister, RedisStateMachinePersister)。你需要提供一个 StateMachinePersist 的实现,告诉 SSM 如何将状态机上下文保存和加载到你的存储中。

例如,使用 JPA 进行持久化:

  1. 添加 JPA 依赖和配置。
  2. 创建一个实体类来存储状态机上下文信息。
  3. 创建 JpaStateMachinePersist 的实现,利用你的 JPA 仓库进行存取操作。
  4. 在配置类中定义 StateMachinePersister Bean,并关联你的 StateMachinePersist 实现。

“`java
// 示例:定义一个简单的 Persist Bean (不依赖具体存储实现,仅为说明)
@Bean
public StateMachinePersist orderStateMachinePersist() {
// 实际应用中,这里会是你的存储逻辑,例如:
// new JpaStateMachinePersist(myJpaRepository);
// 或 new RedisStateMachinePersist(myRedisTemplate);

// 简单示例:使用 Map 模拟存储 (仅用于理解概念,不适用于生产)
Map<String, StateMachineContext<OrderStates, OrderEvents>> map = new HashMap<>();
return new StateMachinePersist<OrderStates, OrderEvents, String>() {
    @Override
    public void write(StateMachineContext<OrderStates, OrderEvents> context, String contextObj) {
        System.out.println("Saving state for context key: " + contextObj + " -> " + context.getState());
        map.put(contextObj, context);
    }

    @Override
    public StateMachineContext<OrderStates, OrderEvents> read(String contextObj) {
         System.out.println("Loading state for context key: " + contextObj);
         return map.get(contextObj);
    }
};

}

// 示例:定义 Persister Bean
@Bean
public StateMachinePersister persister(
StateMachinePersist defaultStateMachinePersist) {
return new DefaultStateMachinePersister<>(defaultStateMachinePersist);
}
“`

使用持久化状态机:

当需要处理特定订单的状态时,你需要先从存储中加载该订单对应的状态机上下文,然后创建或获取一个与该上下文关联的状态机实例,发送事件,最后再将更新后的上下文保存回去。

“`java
// 在 OrderProcessor 中使用 persister
@Autowired
private StateMachinePersister persister;

// 假设订单ID作为上下文key
String orderId = “order-123”;

// 1. 尝试加载状态机上下文
StateMachineContext context = persister.read(orderId);

// 2. 获取或创建状态机实例
StateMachine orderStateMachineForOrder = orderStateMachine; // 示例简单使用同一个Bean,实际生产中可能需要工厂创建独立实例

// 3. 如果加载到了上下文,恢复状态机状态
if (context != null) {
orderStateMachineForOrder.stopReactively().block(); // 停止当前状态机(如果它是自动启动的且是单例)
orderStateMachineForOrder.getStateMachineAccessor()
.doWithAllRegions(function -> function.resetStateMachine(context)); // 恢复状态
orderStateMachineForOrder.startReactively().block(); // 启动恢复后的状态机
System.out.println(“Restored state for order ” + orderId + “: ” + orderStateMachineForOrder.getState().getId());
} else {
// 如果没有加载到,可能是新订单,启动一个新的状态机实例
System.out.println(“No state found for order ” + orderId + “, starting new state machine.”);
orderStateMachineForOrder.startReactively().block(); // 启动新的状态机
// 可选:将初始状态保存
persister.write(orderStateMachineForOrder.getStateMachineAccessor().getStateMachine().getState().getContext(), orderId);
System.out.println(“Initial state for order ” + orderId + “: ” + orderStateMachineForOrder.getState().getId());
}

// 4. 发送事件 (例如,支付事件)
Message payMessage = MessageBuilder
.withPayload(PAY)
.setHeader(“orderId”, orderId) // 将orderId放入头部,Action/Guard可以使用
.build();
orderStateMachineForOrder.sendEvent(payMessage);

// 5. 保存当前状态机上下文
persister.write(orderStateMachineForOrder.getStateMachineAccessor().getStateMachine().getState().getContext(), orderId);
System.out.println(“State after PAY for order ” + orderId + “: ” + orderStateMachineForOrder.getState().getId());
“`

持久化是构建健壮、长生命周期状态机的关键。

4.3 监听器 (Listeners)

StateMachineListener 允许你在状态机生命周期中的各种事件发生时得到通知,例如:
* 状态变化 (stateChanged)
* 转换开始/结束 (transitionStarted, transitionEnded)
* 事件未被处理 (eventNotAccepted)
* 状态机启动/停止 (stateMachineStarted, stateMachineStopped)
* 发生错误 (stateMachineError)

这对于日志记录、监控或触发与状态机核心逻辑分离的副作用非常有用。

“`java
// 在 OrderStateMachineConfig 中定义 listener Bean
@Bean
public StateMachineListener orderStateMachineListener() {
return new StateMachineListenerAdapter() {
@Override
public void stateChanged(State from, State to) {
System.out.printf(“Listener: State changed from %s to %s%n”, from == null ? “null” : from.getId(), to.getId());
}

    @Override
    public void transitionEnded(Transition<OrderStates, OrderEvents> transition) {
         System.out.printf("Listener: Transition ended: %s%n", transition);
    }

    @Override
    public void eventNotAccepted(Message<OrderEvents> event) {
         System.out.printf("Listener: Event not accepted: %s%n", event.getPayload());
    }
};

}

// 在 configure(StateMachineConfigurationConfigurer) 中添加监听器
@Override
public void configure(StateMachineConfigurationConfigurer config) throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(orderStateMachineListener()); // 添加监听器
}
“`
当状态机发生相应的事件时,监听器中对应的方法就会被调用。

4.4 其他高级特性

  • 拦截器 (Interceptors): 允许你在事件被发送到状态机之前或之后,以及转换发生之前或之后进行干预。比 Listener 更强大,可以改变事件或阻止转换。
  • 工厂 (Factories): SSM 提供了工厂接口来创建状态机实例,这在你需要为不同的业务实体(如每个订单)创建独立的状态机实例并进行持久化时非常重要。
  • 分布式状态机 (Distributed State Machines): 利用 ZooKeeper 或 Redis 实现状态机的分布式协调,确保在分布式环境中状态的正确性。

第五部分:何时使用 Spring State Machine

Spring State Machine 不是银弹,它最适合处理那些具有明确、有限、且规则驱动的状态流转的业务场景。

适合使用 SSM 的场景:

  • 复杂的业务流程审批(请假、报销、合同审批等)
  • 订单生命周期管理(电商、物流)
  • 支付流程(交易状态变化)
  • 设备或服务状态监控与控制
  • 游戏状态管理
  • 编译器或解析器中的状态分析

不适合使用 SSM 的场景:

  • 简单的、只有少量状态且转换规则非常基础的场景(可能直接用一个状态字段加简单的逻辑更快捷)。
  • 状态数量巨大且动态变化的场景(有限状态机的前提是状态是有限的)。
  • 业务逻辑主要围绕数据 CRUD,没有明显的、受事件驱动的状态流转。

在使用 SSM 之前,最好先将业务流程的状态和转换规则可视化,例如绘制状态图。如果状态图清晰且符合有限状态机的模型,那么 SSM 将是管理这一逻辑的有力工具。

总结

Spring State Machine 提供了一个强大、灵活且符合 Spring 习惯的框架来构建和管理复杂的状态机。通过将状态、事件、转换、Action 和 Guard 等核心概念进行模型化和声明式配置,SSM 极大地提高了状态流转逻辑的可读性、可维护性和可测试性。

本文从状态机的基础概念出发,逐步深入到 Spring State Machine 的核心组件、基本配置和使用方法,并通过一个订单示例详细展示了如何定义状态、事件、转换,以及如何集成 Action 和 Guard。最后,简要介绍了层次化状态机、持久化和监听器等高级特性。

掌握 Spring State Machine,将帮助你更优雅地处理应用中复杂的流程和对象生命周期管理,避免陷入“面条代码”的困境,构建出更加健壮和易于扩展的系统。

下一步,你可以尝试在自己的项目中实践一个简单的状态机,探索 SSM 的高级功能,并参考 Spring State Machine 的官方文档获取更深入的细节和更多的示例。祝你在状态机的世界里探索愉快!


发表评论

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

滚动至顶部