Spring State Machine 介绍与实战 – wiki基地


Spring State Machine 介绍与实战

在复杂的软件系统中,业务流程往往涉及一系列明确的状态(State)以及在特定条件下由事件(Event)触发的状态转换(Transition)。例如,一个订单从“待支付”到“已支付”,再到“已发货”;一个文档从“草稿”到“待审批”,再到“已批准”或“已拒绝”。这些流程天然地可以用“状态机”的概念来建模。

传统的做法,我们可能会使用大量的 if-elseswitch-case 语句来处理不同状态下的逻辑和事件响应。然而,随着状态和事件数量的增加,这种方式会迅速导致代码变得臃肿、难以理解、难以维护,且容易出错,形成所谓的“意大利面条式代码”。

Spring State Machine(简称 SSM)是 Spring 项目族中的一员,它提供了一种优雅、结构化的方式来构建和管理状态机。通过 SSM,我们可以清晰地定义状态、事件以及它们之间的转换关系,将状态流转的逻辑与具体的业务处理(动作)分离,从而提高代码的可读性、可维护性和可测试性。

本文将详细介绍 Spring State Machine 的核心概念、优势,并通过一个实际的订单处理流程来展示如何使用 SSM 进行开发,最后探讨一些进阶话题。

1. 什么是状态机?(理论基础)

在计算机科学中,状态机(State Machine),更准确地说是有限状态机(Finite State Machine, FSM),是一个数学模型,它描述了对象或系统的行为。一个有限状态机通常包含:

  • 状态(States): 系统在某个时刻可以处于的离散的、有限的集合。
  • 事件(Events): 触发状态转换的外部输入或内部信号。
  • 转换(Transitions): 定义了当系统处于某个状态时,接收到某个事件后,系统将如何响应(可能执行一些动作)并迁移到另一个状态。
  • 初始状态(Initial State): 系统启动时所处的第一个状态。
  • 终止状态(Final State / End State): 系统完成任务后所处的状态(可选)。

简单来说,状态机就是描述系统如何在不同状态之间,根据接收到的事件进行迁移。

为什么不直接使用 if/elseswitch

考虑一个简单的流程:A -> B -> C。这用 if/else 很容易实现。
如果流程复杂化:A -> B -> C, A -> D -> E, B -> F, D -> F, F -> G…
这时,你需要在每个状态的处理逻辑中判断当前状态,然后根据事件类型决定下一个状态,并在转换前后执行相关动作。代码中会充斥着大量的条件判断,并且状态的流转关系变得不直观。修改或添加新的状态/事件/转换会变得非常困难,容易引入 bug。

状态机提供了一种声明式的方式来定义这些关系。你可以画一个状态图,然后将这个图直接映射到 SSM 的配置中。这使得状态流程一目了然,修改也更容易集中管理。

2. Spring State Machine 核心概念

Spring State Machine 构建在 Spring 框架之上,充分利用了 Spring 的依赖注入、配置管理等特性。它提供了以下核心概念和组件:

  • State (状态):
    表示系统在某个时刻所处的状态。在 SSM 中,状态可以是简单的枚举类型(推荐)或字符串。一个状态可以有进入(Entry)和退出(Exit)动作。

    • Entry Action: 进入该状态时执行的动作。
    • Exit Action: 离开该状态时执行的动作。
  • Event (事件):
    触发状态转换的信号。同样,事件通常也是枚举类型或字符串。当一个事件被发送给状态机时,状态机根据当前状态和接收到的事件查找匹配的转换规则。

  • Transition (迁移/转换):
    定义了从一个状态到另一个状态的路径,由一个事件触发。

    • Source State: 转换前的状态。
    • Target State: 转换后的状态。
    • Event: 触发此转换的事件。
    • Action: 转换过程中执行的动作。这是一种特殊的动作,发生在状态退出动作之后,状态进入动作之前。
    • Guard: 守卫。一个布尔表达式,只有当守卫返回 true 时,该转换才会被允许执行。这提供了基于条件的转换能力。
  • StateMachine (状态机实例):
    运行时的一个状态机实例。你可以创建多个状态机实例,每个实例可以独立地跟踪其当前状态。StateMachine 接口提供了发送事件 (sendEvent)、启动 (start)、停止 (stop) 等方法。

  • StateMachineFactory (状态机工厂):
    用于创建 StateMachine 实例的工厂。通常,我们会配置一个状态机模型(定义了所有状态、事件和转换),然后通过 StateMachineFactory 根据这个模型创建运行时实例。Spring Boot 会自动配置一个 StateMachineFactory bean。

  • StateMachineConfiguration (状态机配置):
    定义了状态机的所有规则,包括状态、事件、转换、初始状态、监听器等。通常通过继承 StateMachineConfigurerAdapter 并重写配置方法来实现。

  • Action (动作):
    在状态进入、退出或转换过程中执行的业务逻辑。动作可以是任何实现了 Action 接口的类,通常是 Spring Bean,以便注入其他依赖。

  • Guard (守卫):
    一个布尔函数,用于判断一个转换是否可以发生。它接收 StateContext 作为参数,可以从中获取扩展状态(ExtendedState)或其他信息来做出判断。守卫必须返回 truefalse

  • ExtendedState (扩展状态/上下文):
    一个存储状态机实例上下文数据的机制。可以在 ExtendedState 中放入任何业务相关的数据,这些数据可以在 ActionGuard 中访问和修改。这非常有用,例如在订单状态机中存储订单ID、金额等信息。

  • StateMachineListener (状态机监听器):
    用于监听状态机生命周期中的各种事件,如状态改变、迁移发生、错误发生等。可以实现 StateMachineListener 接口来创建自定义监听器,用于日志记录、监控或触发其他系统行为。

  • Persister (持久化器):
    用于保存和恢复状态机的当前状态和扩展状态。这对于需要跨应用重启或分布式环境保持状态非常重要。SSM 提供了不同的持久化实现,如基于内存、JPA、Redis 等。

3. Spring State Machine 的优势与适用场景

优势:

  1. 清晰的结构: 将状态、事件、转换以及动作、守卫等逻辑 cleanly 分开,提高了代码的可读性和可维护性。
  2. 可视化建模: 状态机配置可以直接映射到 UML 状态图,易于理解和沟通。
  3. 复杂度管理: 有效地管理复杂的状态流转逻辑,避免了大量的嵌套条件判断。
  4. 可测试性: 由于状态逻辑与业务动作分离,可以更容易地对状态转换进行单元测试。
  5. 丰富的特性: 支持嵌套状态、并行状态、历史状态、外部事件、持久化等高级特性。
  6. Spring 生态整合: 天然集成 Spring 的依赖注入、事务管理等功能。

适用场景:

  • 业务流程审批: 文档审批、请假流程、报销流程等,有明确的审批环节和状态。
  • 订单生命周期管理: 订单从创建到支付、发货、收货、完成或取消等全过程。
  • 支付流程: 支付请求、支付中、支付成功、支付失败、退款等。
  • 工作流引擎: 作为轻量级的工作流引擎核心。
  • 游戏逻辑: 回合制游戏的玩家状态、游戏阶段等。
  • 设备状态管理: 设备从启动、运行、暂停、故障、关闭等状态。
  • 协议解析/状态跟踪: 需要根据接收到的数据包或信号在不同状态间切换的场景。

4. 实战:构建一个订单状态机

我们将构建一个简单的订单处理状态机,包含以下状态和事件:

状态 (States):

  • DRAFT (草稿)
  • SUBMITTED (已提交)
  • PAID (已支付)
  • SHIPPED (已发货)
  • DELIVERED (已送达)
  • COMPLETED (已完成)
  • CANCELLED (已取消)

事件 (Events):

  • SUBMIT (提交订单)
  • PAY (支付订单)
  • SHIP (发货)
  • DELIVER (确认送达)
  • CANCEL (取消订单)
  • EXPIRE (订单过期,仅从DRAFT/SUBMITTED转CANCELLED)

转换 (Transitions):

  • DRAFT -> SUBMITTED (SUBMIT 事件)
  • SUBMITTED -> PAID (PAY 事件)
  • SUBMITTED -> CANCELLED (CANCELEXPIRE 事件)
  • PAID -> SHIPPED (SHIP 事件)
  • PAID -> CANCELLED (CANCEL 事件)
  • SHIPPED -> DELIVERED (DELIVER 事件)
  • DELIVERED -> COMPLETED (无事件,自动迁移,或者接收一个COMPLETE事件)
  • DRAFT -> CANCELLED (CANCELEXPIRE 事件)

我们还将添加一些动作和守卫:

  • SUBMIT 事件触发时,执行一个动作记录提交时间。
  • PAY 事件触发时,执行一个动作更新支付信息。
  • PAIDSHIPPED 的转换需要一个守卫,检查是否有足够的库存。
  • 进入 COMPLETED 状态时,执行一个动作标记订单最终完成。
  • 进入 CANCELLED 状态时,执行一个动作处理退款或库存回滚。

项目设置:

使用 Spring Boot 初始化一个项目,添加 spring-boot-starterspring-statemachine-core 依赖。

“`xml


org.springframework.boot
spring-boot-starter


org.springframework.statemachine
spring-statemachine-core
3.2.0

“`

定义状态和事件枚举:

“`java
// OrderStates.java
public enum OrderStates {
DRAFT, // 草稿
SUBMITTED, // 已提交
PAID, // 已支付
SHIPPED, // 已发货
DELIVERED, // 已送达
COMPLETED, // 已完成
CANCELLED // 已取消
}

// OrderEvents.java
public enum OrderEvents {
SUBMIT, // 提交订单
PAY, // 支付订单
SHIP, // 发货
DELIVER, // 确认送达
CANCEL, // 取消订单
EXPIRE // 订单过期
}
“`

定义状态机配置:

创建一个配置类,继承 StateMachineConfigurerAdapter

“`java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;

import java.util.EnumSet;

@Configuration
@EnableStateMachineFactory // 启用状态机工厂
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter {

// 1. 配置状态
@Override
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
states
.withStates()
.initial(OrderStates.DRAFT) // 定义初始状态
.states(EnumSet.allOf(OrderStates.class)) // 定义所有可能的状态
// 可以为特定状态配置进入/退出动作
.stateEntry(OrderStates.SUBMITTED, submitEntryAction()) // 进入SUBMITTED状态时执行动作
.stateEntry(OrderStates.PAID, paidEntryAction())
.stateEntry(OrderStates.COMPLETED, completedEntryAction())
.stateEntry(OrderStates.CANCELLED, cancelledEntryAction())
.stateExit(OrderStates.DRAFT, draftExitAction()); // 退出DRAFT状态时执行动作
}
// 2. 配置状态转换
@Override
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
transitions
// DRAFT -> SUBMITTED by SUBMIT
.withExternal()
.source(OrderStates.DRAFT).target(OrderStates.SUBMITTED).event(OrderEvents.SUBMIT)
.action(submitTransitionAction()) // 转换时的动作
.and()
// SUBMITTED -> PAID by PAY
.withExternal()
.source(OrderStates.SUBMITTED).target(OrderStates.PAID).event(OrderEvents.PAY)
.action(payTransitionAction())
.and()
// SUBMITTED -> CANCELLED by CANCEL or EXPIRE
.withExternal()
.source(OrderStates.SUBMITTED).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
.action(cancelTransitionAction())
.and()
.withExternal()
.source(OrderStates.SUBMITTED).target(OrderStates.CANCELLED).event(OrderEvents.EXPIRE)
.action(cancelTransitionAction())
.and()
// PAID -> SHIPPED by SHIP with guard
.withExternal()
.source(OrderStates.PAID).target(OrderStates.SHIPPED).event(OrderEvents.SHIP)
.guard(inventoryGuard()) // 添加守卫
.action(shipTransitionAction())
.and()
// PAID -> CANCELLED by CANCEL
.withExternal()
.source(OrderStates.PAID).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
.action(cancelTransitionAction()) // 可能需要退款逻辑
.and()
// SHIPPED -> DELIVERED by DELIVER
.withExternal()
.source(OrderStates.SHIPPED).target(OrderStates.DELIVERED).event(OrderEvents.DELIVER)
.action(deliverTransitionAction())
.and()
// DELIVERED -> COMPLETED (可以设计为自动迁移,或者通过事件触发)
// 这里设计为通过一个隐含的'COMPLETE'事件(虽然我们没在枚举中定义,但可以手动发送或在deliverTransitionAction后触发)
// 或者更简单,直接在DELIVERED状态entry/exit中触发到COMPLETED的迁移,或者用内部转换+延迟
// 为了示例清晰,我们假设DELIVERED后自动完成,或者是一个特殊的外部事件(比如定时任务检查)
// 这里我们简化,假设DELIVERED状态的进入动作会触发后续完成逻辑
// 或者,我们可以定义一个从DELIVERED到COMPLETED的无事件自动转换(但SSM中无事件外部转换需要特殊处理,通常依赖listener或action中触发)
// 最直观的方式是DELIVER事件完成DELIVERED状态,然后自动/通过action触发COMPLETED。
// 这里我们用一种常见模式:DELIVERED的entry action触发到COMPLETED的过渡(如果DELIVERED本身不是最终状态)
// 但通常DELIVERED已经是半最终状态,COMPLETE更多是业务上的标识。
// 我们修改设计:DELIVERED即代表送达,COMPLETED是一个最终标记,可能通过DELIVERED的entry action或另一个事件完成
// 简单起见,让DELIVER事件直接到DELIVERED,进入DELIVERED状态后,业务逻辑认为流程基本结束,COMPLETED只是标记。
// 或者, DELIVER -> DELIVERED, DELIVERED -> COMPLETED 由一个单独的COMPLETE事件触发(更符合SSM事件驱动模型)
// 我们添加 COMPLETED 状态作为最终状态,由 DELIVER 事件触发的迁移的 Action 里去触发。
// 为了简化配置,我们假设DELIVER事件直接到DELIVERED,并且在DELIVERED状态的Entry Action里执行最终完成逻辑。
// 如果需要COMPLETED状态,可以再加一个转换 DELIVERED -> COMPLETED by COMPLETE_EVENT。
// 考虑到篇幅,我们让DELIVERED为最终状态,COMPLETED的逻辑在DELIVERED的entry action执行。
// 但为了演示COMPLETED状态,我们还是加上 DELIVERED -> COMPLETED 的转换,由一个内部事件或监听器触发,或者假设 DELIVER 事件直接到 COMPLETED (不符合实际)
// 调整:DELIVER 事件到 DELIVERED,然后DELIVERED的entry action触发到COMPLETED的过渡。这是常见的内部触发模式。
.withExternal() // DELIVERED 到 COMPLETED 的转换
.source(OrderStates.DELIVERED).target(OrderStates.COMPLETED)
// 通常这种自动过渡不是由外部事件触发,而是在DELIVERED的entry action中 sendEvent(COMPLETE_EVENT)
// 我们定义一个内部事件来触发它,或者更简单的,用内部转换
// 使用内部转换: DELIVERED 的 entry action 触发到 COMPLETED
// 但DELIVERED已经是外部状态了,内部转换通常用于同一个状态内的自循环或子状态。
// 最符合模型的是: DELIVER 事件 -> DELIVERED 状态。 在 DELIVERED 状态的进入动作中,判断并发送 COMPLETE_EVENT 事件。
// 我们需要定义一个 COMPLETE_EVENT 事件和 DELIVERED -> COMPLETED by COMPLETE_EVENT 转换。
// 添加 COMPLETE 事件到枚举
.and()
// DRAFT -> CANCELLED by CANCEL or EXPIRE
.withExternal()
.source(OrderStates.DRAFT).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
.action(cancelTransitionAction())
.and()
.withExternal()
.source(OrderStates.DRAFT).target(OrderStates.CANCELLED).event(OrderEvents.EXPIRE)
.action(cancelTransitionAction());
// 注意:如果需要 DELIVERED -> COMPLETED 的转换,需要额外的事件和转换配置。
// 比如添加 OrderEvents.COMPLETE 事件,并配置 transition: .source(DELIVERED).target(COMPLETED).event(COMPLETE)
// 在 deliverEntryAction() 中执行逻辑后,可以手动发送 COMPLETE 事件。
}
// 3. 配置状态机监听器 (可选)
@Override
public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
config
.withConfiguration()
.autoStartup(true) // 自动启动状态机
.listener(stateMachineListener()); // 添加监听器
}
// === 定义 Action Beans ===
// 状态进入动作
@Bean
public Action<OrderStates, OrderEvents> submitEntryAction() {
return context -> {
System.out.println("进入状态: " + context.getTarget().getId() + "。订单已提交,等待支付。");
// 在扩展状态中记录提交时间或其他信息
context.getExtendedState().getVariables().put("submitTime", System.currentTimeMillis());
Long orderId = context.getExtendedState().get("orderId", Long.class);
System.out.println("订单ID: " + orderId + " 提交时间: " + context.getExtendedState().get("submitTime"));
};
}
@Bean
public Action<OrderStates, OrderEvents> paidEntryAction() {
return context -> {
System.out.println("进入状态: " + context.getTarget().getId() + "。订单已支付,等待发货。");
Long orderId = context.getExtendedState().get("orderId", Long.class);
System.out.println("订单ID: " + orderId + " 支付成功。");
// 处理支付成功后的业务逻辑,如通知仓库发货等
};
}
@Bean
public Action<OrderStates, OrderEvents> completedEntryAction() {
return context -> {
System.out.println("进入状态: " + context.getTarget().getId() + "。订单已完成。");
Long orderId = context.getExtendedState().get("orderId", Long.class);
System.out.println("订单ID: " + orderId + " 已最终完成。");
// 处理订单完成后的业务逻辑
};
}
@Bean
public Action<OrderStates, OrderEvents> cancelledEntryAction() {
return context -> {
System.out.println("进入状态: " + context.getTarget().getId() + "。订单已取消。");
Long orderId = context.getExtendedState().get("orderId", Long.class);
System.out.println("订单ID: " + orderId + " 已取消。");
// 处理取消订单后的业务逻辑,如库存回滚、退款等
};
}
// 状态退出动作
@Bean
public Action<OrderStates, OrderEvents> draftExitAction() {
return context -> {
System.out.println("退出状态: " + context.getSource().getId() + "。草稿阶段结束。");
};
}
// 转换动作
@Bean
public Action<OrderStates, OrderEvents> submitTransitionAction() {
return context -> {
System.out.println("执行转换动作: " + context.getSource().getId() + " -> " + context.getTarget().getId() + " (事件: " + context.getEvent() + ")");
// 可以在此执行与转换本身相关的轻量级逻辑
};
}
@Bean
public Action<OrderStates, OrderEvents> payTransitionAction() {
return context -> {
System.out.println("执行转换动作: " + context.getSource().getId() + " -> " + context.getTarget().getId() + " (事件: " + context.getEvent() + ")");
};
}
@Bean
public Action<OrderStates, OrderEvents> cancelTransitionAction() {
return context -> {
System.out.println("执行转换动作: " + context.getSource().getId() + " -> " + context.getTarget().getId() + " (事件: " + context.getEvent() + ")");
};
}
@Bean
public Action<OrderStates, OrderEvents> shipTransitionAction() {
return context -> {
System.out.println("执行转换动作: " + context.getSource().getId() + " -> " + context.getTarget().getId() + " (事件: " + context.getEvent() + ")");
// 在真实场景中,这里会更新订单状态到数据库,通知物流系统等
};
}
@Bean
public Action<OrderStates, OrderEvents> deliverTransitionAction() {
return context -> {
System.out.println("执行转换动作: " + context.getSource().getId() + " -> " + context.getTarget().getId() + " (事件: " + context.getEvent() + ")");
// 送达后的业务逻辑,例如触发自动确认收货或发送评价提醒
// 在这里可以考虑发送 COMPLETE 事件触发 DELIVERED -> COMPLETED 转换
// context.getStateMachine().sendEvent(OrderEvents.COMPLETE); // 如果有 COMPLETE 事件的话
};
}
// === 定义 Guard Beans ===
@Bean
public Guard<OrderStates, OrderEvents> inventoryGuard() {
return context -> {
// 假设从扩展状态中获取订单ID或商品信息,检查库存
Long orderId = context.getExtendedState().get("orderId", Long.class);
System.out.println("执行库存守卫,检查订单: " + orderId);
// 模拟库存检查逻辑
boolean hasStock = true; // 假设总是有库存
if (!hasStock) {
System.out.println("库存不足,阻止发货");
} else {
System.out.println("库存充足,允许发货");
}
return hasStock;
};
}
// === 定义 Listener Bean ===
@Bean
public StateMachineListener<OrderStates, OrderEvents> stateMachineListener() {
return new StateMachineListener<OrderStates, OrderEvents>() {
@Override
public void stateChanged(org.springframework.statemachine.state.State<OrderStates, OrderEvents> from, org.springframework.statemachine.state.State<OrderStates, OrderEvents> to) {
if (from != null) {
System.out.println("状态改变: 从 " + from.getId() + " 到 " + to.getId());
} else {
System.out.println("状态初始化/改变到: " + to.getId());
}
}
@Override
public void stateEntered(org.springframework.statemachine.state.State<OrderStates, OrderEvents> state) {} // 已在 Entry Action 处理
@Override
public void stateExited(org.springframework.statemachine.state.State<OrderStates, OrderEvents> state) {} // 已在 Exit Action 处理
@Override
public void eventNotAccepted(OrderEvents event) {
System.out.println("事件未被接受: " + event);
}
@Override
public void transition(org.springframework.statemachine.transition.Transition<OrderStates, OrderEvents> transition) {
if (transition != null && transition.getSource() != null && transition.getTarget() != null) {
System.out.println("迁移发生: " + transition.getSource().getId() + " -> " + transition.getTarget().getId() + " (事件: " + (transition.getEvent() != null ? transition.getEvent() : "无事件")) + ")");
}
}
@Override
public void transitionStarted(org.springframework.statemachine.transition.Transition<OrderStates, OrderEvents> transition) {}
@Override
public void transitionEnded(org.springframework.statemachine.transition.Transition<OrderStates, OrderEvents> transition) {}
@Override
public void stateMachineStarted(StateMachine<OrderStates, OrderEvents> stateMachine) {
System.out.println("状态机已启动");
}
@Override
public void stateMachineStopped(StateMachine<OrderStates, OrderEvents> stateMachine) {
System.out.println("状态机已停止");
}
@Override
public void stateMachineError(StateMachine<OrderStates, OrderEvents> stateMachine, Exception exception) {
System.err.println("状态机错误: " + exception.getMessage());
}
@Override
public void extendedStateChanged(Object key, Object value) {
// System.out.println("ExtendedState改变: Key=" + key + ", Value=" + value); // 可能会非常频繁,按需开启
}
@Override
public void stateMachineForcedTransition(org.springframework.statemachine.transition.Transition<OrderStates, OrderEvents> transition) {}
};
}

}
“`

使用状态机:

在 Spring Bean 中注入 StateMachineFactory,然后创建和使用状态机实例。

“`java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;

@SpringBootApplication
public class StateMachineDemoApplication implements CommandLineRunner {

@Autowired
private StateMachineFactory<OrderStates, OrderEvents> stateMachineFactory;
public static void main(String[] args) {
SpringApplication.run(StateMachineDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("--- 订单流程开始 ---");
// 创建一个新的状态机实例,模拟一个订单
// 通常,每个业务对象(如订单)对应一个状态机实例
StateMachine<OrderStates, OrderEvents> orderStateMachine = stateMachineFactory.getUuidStateMachine();
// 启动状态机,进入初始状态 DRAFT
orderStateMachine.start();
System.out.println("当前状态: " + orderStateMachine.getState().getId());
// 在扩展状态中设置订单ID,可以在 Action 和 Guard 中访问
Long orderId = 12345L;
orderStateMachine.getExtendedState().getVariables().put("orderId", orderId);
System.out.println("设置订单ID: " + orderId);
// 发送 SUBMIT 事件
System.out.println("\n发送事件: " + OrderEvents.SUBMIT);
// 可以通过 Message 携带额外信息,这些信息可以在 StateContext 中获取
Message<OrderEvents> submitMessage = MessageBuilder.withPayload(OrderEvents.SUBMIT)
.setHeader("operator", "user1") // 示例头部信息
.build();
orderStateMachine.sendEvent(submitMessage);
System.out.println("当前状态: " + orderStateMachine.getState().getId());
// 发送 PAY 事件
System.out.println("\n发送事件: " + OrderEvents.PAY);
Message<OrderEvents> payMessage = MessageBuilder.withPayload(OrderEvents.PAY)
.setHeader("amount", 100.0)
.build();
orderStateMachine.sendEvent(payMessage);
System.out.println("当前状态: " + orderStateMachine.getState().getId());
// 发送 SHIP 事件 (会触发库存守卫)
System.out.println("\n发送事件: " + OrderEvents.SHIP);
Message<OrderEvents> shipMessage = MessageBuilder.withPayload(OrderEvents.SHIP)
.build();
orderStateMachine.sendEvent(shipMessage);
System.out.println("当前状态: " + orderStateMachine.getState().getId()); // 如果守卫失败,状态不变
// 发送 DELIVER 事件
System.out.println("\n发送事件: " + OrderEvents.DELIVER);
Message<OrderEvents> deliverMessage = MessageBuilder.withPayload(OrderEvents.DELIVER)
.build();
orderStateMachine.sendEvent(deliverMessage);
System.out.println("当前状态: " + orderStateMachine.getState().getId());
// 现在,模拟发送一个不允许的事件,比如再次支付
System.out.println("\n发送不允许的事件: " + OrderEvents.PAY);
Message<OrderEvents> payAgainMessage = MessageBuilder.withPayload(OrderEvents.PAY)
.build();
orderStateMachine.sendEvent(payAgainMessage); // Listener会打印 eventNotAccepted
System.out.println("当前状态: " + orderStateMachine.getState().getId()); // 状态应该不变
// 模拟取消订单流程 (从 SUBMITTED 状态取消)
System.out.println("\n--- 模拟订单取消流程 ---");
StateMachine<OrderStates, OrderEvents> orderStateMachine2 = stateMachineFactory.getUuidStateMachine();
orderStateMachine2.start();
orderStateMachine2.getExtendedState().getVariables().put("orderId", 54321L); // 另一个订单ID
System.out.println("当前状态(订单2): " + orderStateMachine2.getState().getId());
orderStateMachine2.sendEvent(OrderEvents.SUBMIT);
System.out.println("当前状态(订单2): " + orderStateMachine2.getState().getId());
orderStateMachine2.sendEvent(OrderEvents.CANCEL); // 从SUBMITTED取消
System.out.println("当前状态(订单2): " + orderStateMachine2.getState().getId());
// 停止状态机 (如果需要)
// orderStateMachine.stop();
System.out.println("\n--- 订单流程结束 ---");
}

}
“`

运行 StateMachineDemoApplication,你将在控制台看到状态机的启动、状态变化、事件发送、动作执行以及监听器的输出。

代码解释:

  • OrderStateMachineConfig 类定义了状态机的结构。
  • configure(StateMachineStateConfigurer) 定义了所有可能的状态以及初始状态。可以通过 .stateEntry().stateExit() 配置进入和退出特定状态时执行的动作。
  • configure(StateMachineTransitionConfigurer) 定义了所有状态之间的转换规则。.withExternal() 表示外部转换(状态会改变)。.source().target() 指定起始和目标状态,.event() 指定触发事件。.action() 配置转换过程中执行的动作。.guard() 配置转换的守卫。
  • configure(StateMachineConfigurationConfigurer) 配置状态机的全局行为,如是否自动启动、监听器等。
  • 所有 @Bean 方法定义的 ActionGuard 实例都是 Spring 管理的 Bean,可以方便地注入其他服务。StateContext 对象在 ActionGuard 中提供上下文信息,包括当前状态、事件、消息头以及扩展状态(ExtendedState)。
  • StateMachineListener 监听器打印出状态变化、事件未接受等信息,有助于调试和监控。
  • StateMachineDemoApplication 中,我们注入 StateMachineFactory 并使用它创建 StateMachine 实例。getUuidStateMachine() 会创建一个具有唯一ID的状态机实例。
  • stateMachine.start() 启动状态机,使其进入初始状态并执行初始状态的 Entry Action。
  • stateMachine.sendEvent() 发送事件来触发状态转换。可以发送简单的枚举,也可以通过 MessageBuilder 构建包含头部信息的 Message
  • stateMachine.getState().getId() 获取当前状态。
  • stateMachine.getExtendedState().getVariables() 访问和修改扩展状态中的变量。

通过这个例子,我们可以看到 SSM 如何将复杂的状态流转逻辑清晰地表达出来,并与具体的业务动作解耦。

5. 进阶话题

a. 持久化 (Persistence):

在实际应用中,状态机的当前状态和扩展状态需要持久化,以便在服务重启后恢复,或在分布式环境中共享状态。SSM 提供了 StateMachinePersister 接口和多种实现。

  • 内存持久化: InMemoryStateMachinePersister,适合简单的场景或测试。
  • JPA 持久化: 需要配置 JPA 实体来存储状态机信息。
  • Redis 持久化: RedisStateMachineContextRepositoryRedisStateMachinePersister,适合分布式环境。

使用持久化通常涉及:

  1. 配置一个 StateMachinePersister Bean。
  2. 在状态机需要保存状态时(例如,每次状态转换后)调用 persister.persist(stateMachine, contextObj),其中 contextObj 是用于唯一标识该状态机实例的业务对象(如订单ID)。
  3. 在需要恢复状态机时,通过 persister.restore(stateMachine, contextObj) 根据 contextObj 加载保存的状态。

b. 嵌套状态 (Nested States):

状态可以包含子状态,形成层级结构。这对于表示具有内部流程的复杂状态非常有用。例如,订单的 PAID 状态可能包含 PaymentConfirmed, AllocatingStock, ReadyToShip 等子状态。只有当子状态机达到其最终状态时,外部状态机的转换才可能发生。

配置嵌套状态使用 .withStates().parent(parentId).initial(initialChildState).states(childStates...)

c. 并行状态 (Parallel States):

状态机可以同时处于多个正交区域(Region)中的状态。这适用于系统中多个独立但相关的并行流程。例如,订单支付后,可能同时进入“库存分配中”和“物流准备中”两个并行状态,只有当两者都完成后,订单才进入“待发货”状态。

配置并行状态使用 .withStates().fork(forkState).regions(...).join(joinState).

d. 历史状态 (History States):

用于处理从嵌套状态返回到其父状态时,记住上次离开父状态时所处的子状态。当再次进入父状态时,状态机不是进入父状态的初始子状态,而是进入历史状态所记录的子状态。

配置历史状态使用 .withStates().history(historyState, HistoryMode.DEEP/shallow).

e. 监听器 (Listeners):

除了在配置中直接添加 StateMachineListener Bean,你也可以在运行时动态地给状态机实例添加监听器。监听器提供了细粒度的事件回调,可以用于日志记录、审计、监控或触发外部系统调用。

f. 错误处理:

可以通过配置 .stateMachineError() 或在 StateMachineListenerstateMachineError 方法中处理状态机内部发生的异常。对于事件未被接受的情况,eventNotAccepted 监听器方法非常有用。

6. 局限性与注意事项

  • 简单场景的开销: 对于状态和事件非常简单的流程,引入 SSM 可能会显得过度设计,使用简单的 if/else 或策略模式可能更直接。
  • 配置复杂度: 随着状态和转换的数量急剧增加,状态机配置类可能会变得非常庞大和复杂。合理使用嵌套状态和并行状态可以帮助管理复杂度,但过分复杂的图本身就是挑战。
  • 可视化工具: SSM 本身不提供图形化建模工具,但其配置结构便于映射到标准的状态图工具(如 PlantUML, draw.io)进行可视化。
  • 调试: 调试状态机流程可能需要结合监听器输出和 IDE 调试来跟踪状态变化和动作执行顺序。

7. 总结

Spring State Machine 提供了一个强大且灵活的框架,用于在 Spring 应用中构建和管理状态驱动的业务逻辑。它通过清晰地定义状态、事件和转换,有效地将状态流转的控制逻辑与具体的业务实现(动作和守卫)分离,极大地提高了代码的可读性、可维护性和可测试性。

通过本文的介绍和订单状态机的实战示例,你应该对 SSM 的核心概念和基本用法有了深入了解。掌握如何配置状态、事件、转换、动作、守卫以及如何使用 StateMachineFactory 创建和操作状态机实例,是入门 SSM 的关键。

对于需要处理复杂业务流程、工作流或任何具有明确生命周期的实体的应用,Spring State Machine 都是一个值得考虑的优秀解决方案。结合持久化等进阶特性,它可以帮助你构建健壮、可伸缩的状态管理系统。

希望本文能为你学习和使用 Spring State Machine 提供有价值的帮助!


发表评论

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

滚动至顶部