状态机——Spring State Machine

Spring State Machine

1、概述

相比于轻量级的 Cola-StateMachine,Spring State Machine(Spring 状态机) 是 Spring 生态中功能最强大、最完备,同时也是体量最重的状态机框架。

它不仅支持基础的状态流转,还支持嵌套状态(Substates)并行状态(Orthogonal Regions)状态持久化伪状态(Choice/Junction)事件监听拦截器以及与 Spring SecuritySpring Shell 的无缝集成。在需要严格事务控制、分布式状态恢复以及复杂工作流的大型企业级架构中,它是不可替代的利器。

在开始写代码前,必须理清 Spring StateMachine 的根本设计哲学:它是**"有状态(Stateful)"的**。

  • Cola-StateMachine:单例运行,状态机只存规则,不存当前状态,每次调用要传入当前状态。
  • Spring StateMachine:每一个订单实体在内存中都对应一个独立的、有生命周期的状态机实例。它死死记得自己当前在哪个状态,你只需要对它 sendEvent(Event),它自己就会在内存中推进。

2、简单示例

2.1、引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>3.2.0</version>
</dependency>

2.2、定义状态与事件枚举

java 复制代码
public enum OrderState {
    //待付款
    PENDING_PAY,
    //待发货
    PENDING_DELIVER,
    //待收货
    PENDING_RECEIVE,
    //待评价
    PENDING_COMMENT,
    //已完成
    COMPLETED,
    //已取消
    CANCELLED
}
java 复制代码
public enum OrderEvent {
    //用户支付
    USER_PAY, 
    //商家发货
    MERCHANT_DELIVER, 
    //收到包裹
    RECEIVE_PACKAGE, 
    //评价
    COMMENT, 
    //超时未评
    COMMENT_TIMEOUT, 
    //手动取消
    MANUAL_CANCEL, 
    //超时未支付
    PAY_TIMEOUT
}

2.3、编写状态机配置类

我们需要继承 StateMachineConfigurerAdapter,全面配置状态机的三大要素:

  • 初始状态
  • 所有状态节点
  • 流转规则
  • 拦截器(Guard/Action)。
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.guard.Guard;
import org.springframework.statemachine.action.Action;

import java.util.EnumSet;

@Configuration
// 注意:企业级开发中,绝不要用 @EnableStateMachine(单例),必须用 @EnableStateMachineFactory(状态机工厂)
@EnableStateMachineFactory(name = "orderStateMachineFactory")
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {

    /**
     * 1. 配置状态节点的初始化与终态
     */
    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
        states.withStates()
              .initial(OrderState.PENDING_PAY) // 对应图中的 [开始] -> 进入 [待付款]
              .end(OrderState.COMPLETED)        // 终态 [已完成] -> 对应图中的 [结束]
              .end(OrderState.CANCELLED)         // 终态 [已取消] -> 对应图中的 [结束]
              .states(EnumSet.allOf(OrderState.class)); // 注册所有状态
    }

    /**
     * 2. 配置状态流转路由规则(完全对应你的状态图)
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
        transitions
            // 待付款 -> 待发货 (用户支付)
            .withExternal()
                .source(OrderState.PENDING_PAY).target(OrderState.PENDING_DELIVER)
                .event(OrderEvent.USER_PAY)
                .guard(checkPayGuard())  // 准入条件校验 (相当于Cola的Condition)
                .action(doPayAction())   // 核心业务动作 执行
                .and()
            // 待付款 -> 已取消 (手动取消)
            .withExternal()
                .source(OrderState.PENDING_PAY).target(OrderState.CANCELLED)
                .event(OrderEvent.MANUAL_CANCEL)
                .action(doCancelAction())
                .and()
            // 待付款 -> 已取消 (超时未支付)
            .withExternal()
                .source(OrderState.PENDING_PAY).target(OrderState.CANCELLED)
                .event(OrderEvent.PAY_TIMEOUT)
                .action(doCancelAction())
                .and()
            // 待发货 -> 待收货 (商家发货)
            .withExternal()
                .source(OrderState.PENDING_DELIVER).target(OrderState.PENDING_RECEIVE)
                .event(OrderEvent.MERCHANT_DELIVER)
                .and()
            // 待收货 -> 待评价 (收到包裹)
            .withExternal()
                .source(OrderState.PENDING_RECEIVE).target(OrderState.PENDING_COMMENT)
                .event(OrderEvent.RECEIVE_PACKAGE)
                .and()
            // 待评价 -> 已完成 (评价)
            .withExternal()
                .source(OrderState.PENDING_COMMENT).target(OrderState.COMPLETED)
                .event(OrderEvent.COMMENT)
                .and()
            // 待评价 -> 已完成 (超时未评)
            .withExternal()
                .source(OrderState.PENDING_COMMENT).target(OrderState.COMPLETED)
                .event(OrderEvent.COMMENT_TIMEOUT);
    }

    // ==================== 守卫/条件 (Guard) ====================
    private Guard<OrderState, OrderEvent> checkPayGuard() {
        return context -> {
            // 从状态机消息头中拿到业务传参
            String orderId = context.getMessageHeaders().get("orderId", String.class);
            System.out.println("【Guard校验】检查订单: " + orderId + " 是否有真实的支付流水...");
            return true; // 返回 true 允许放行,false 阻断
        };
    }

    // ==================== 动作执行 (Action) ====================
    private Action<OrderState, OrderEvent> doPayAction() {
        return context -> {
            String orderId = context.getMessageHeaders().get("orderId", String.class);
            System.out.println("【Action核心业务】订单: " + orderId + " 扣减真实库存,推进状态机...");
        };
    }

    private Action<OrderState, OrderEvent> doCancelAction() {
        return context -> System.out.println("【Action核心业务】释放库存,关闭订单。");
    }
}

3、分布式状态持久化

由于 Spring StateMachine 是在内存中维持状态的,分布式部署时,每次操作都必须先从数据库捞出订单状态 -> 还原恢复内存状态机 -> 发送事件推进 -> 把新状态存回数据库

Spring 提供了 StateMachineRuntimePersister 机制来实现这一闭环。

编写自定义的持久化拦截器:

我们利用基础的 DefaultStateMachineContext,在状态机发生质变流转、准备写入内存前,拦截它并直接同步修改数据库。

java 复制代码
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
import org.springframework.statemachine.transition.Transition;
import org.springframework.stereotype.Component;

@Component
public class OrderStateMachineInterceptor extends StateMachineInterceptorAdapter<OrderState, OrderEvent> {

    // 假设这是你的数据库操作 Mapper
    // @Autowired private OrderMapper orderMapper;

    @Override
    public void preStateChange(State<OrderState, OrderEvent> state, Message<OrderEvent> message,
                               Transition<OrderState, OrderEvent> transition, StateMachine<OrderState, OrderEvent> stateMachine,
                               StateMachine<OrderState, OrderEvent> rootStateMachine) {
        
        if (message != null && message.getHeaders().containsKey("orderId")) {
            Long orderId = message.getHeaders().get("orderId", Long.class);
            OrderState targetState = state.getId();
            
            // 【核心持久化步子】状态机即将发生质变前,强制同步更新数据库
            System.out.println("【持久化拦截器】开始持久化,订单ID: " + orderId + ",新状态更新为: " + targetState);
            // orderMapper.updateStatus(orderId, targetState.name());
        }
    }
}

4、业务层(Service)优雅调用

在 Service 层,我们要利用 StateMachineFactory 动态去 new/获取状态机,同时在推进前必须强制用持久化拦截器将数据库的历史状态塞进状态机中进行重置恢复。

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.config.StateMachineFactory;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private StateMachineFactory<OrderState, OrderEvent> orderStateMachineFactory;

    @Autowired
    private OrderStateMachineInterceptor orderStateMachineInterceptor;

    public void fireEvent(Long orderId, OrderEvent event) {
        // 1. 从工厂中获取一台全新的状态机实例(根据机器ID区分)
        StateMachine<OrderState, OrderEvent> stateMachine = orderStateMachineFactory.getStateMachine(String.valueOf(orderId));

        // 2. 【极其重要】停机并解构状态机,强制注入数据库里当前的真实状态进行恢复
        // 模拟从数据库查出来的是 OrderState.PENDING_PAY
        OrderState dbState = OrderState.PENDING_PAY; 
        
        stateMachine.stopReactively().block();
        stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> {
            // 挂载我们上面写的数据库持久化拦截器
            accessor.addStateMachineInterceptor(orderStateMachineInterceptor);
            // 充能恢复内存状态
            accessor.resetStateMachineReactively(
                new DefaultStateMachineContext<>(dbState, null, null, null)
            ).block();
        });
        stateMachine.startReactively().block();

        // 3. 构造带业务参数的消息体(透传给 Action 和 Guard 使用)
        Message<OrderEvent> message = MessageBuilder.withPayload(event)
                .setHeader("orderId", orderId)
                .build();

        // 4. 发送事件,驱动流转
        boolean success = stateMachine.sendEvent(message);
        
        if (!success) {
            throw new RuntimeException("订单状态流转失败,不合法的操作事件!");
        }
    }
}

5、Spring StateMachine 的高阶特性

5.1、伪状态分支选择(Choice / Junction)

在状态图里面,"用户支付"直接去了"待发货"。但在实际业务中,用户付完钱后可能触发风控拦截。Spring 支持类似代码中 if-else 的图纸节点:

java 复制代码
// 配置流转时使用 choice
transitions.withChoice()
    .source(OrderState.PENDING_PAY)
    .first(OrderState.PENDING_DELIVER, checkStockGuard()) // 库存够,去待发货
    .last(OrderState.CANCELLED); // 库存不够,直接去已取消

5.2、嵌套状态(Substates / Hierarchical States)

比如"退款中"是一个大状态,但里面又细分为"买家寄出"、"商家驳回"、"等待小二介入"等子状态。Spring StateMachine 支持在一张图里套另一张图,子状态未能处理的事件会自动向上冒泡给父状态处理。

5.3、并行区域(Orthogonal Regions)

一个复杂的实体(如汽车出厂检查),需要同时满足"外观检查完成"和"发动机检测完成"这两个并行的、互不干扰的状态轴,最后会合(Join)后才能进入"允许出厂"。Spring 状态机可以在单台机器里同时跑多根独立的状态流转线。

6、总结

维度 Spring StateMachine ola-StateMachine
开销与性能 较重。每次请求都要经历:创建/获取 -> 停止 -> 内存恢复 -> 启动 -> 发送事件 -> 拦截写库 的全生命周期,高并发下对内存和性能有一定损耗。 极轻。纯无状态静态路由表,单例运行,几乎没有额外开销。
能丰富度 天花板级别。只有你想不到,没有它做不到的复杂拓扑图。 仅支持基础的外部流转、内部流转。
上手难度 较高,API 较为复杂(全面转向响应式编程 Reactive Streams)。 极低,十分钟即可上手。
相关推荐
程序员Terry3 小时前
博客系统全文搜索实战:用 Elasticsearch 告别 MySQL LIKE 查询
后端·elasticsearch
漓漾li3 小时前
每日面试题(2026-05-20)- GO AI agent全栈
后端·架构·go
XS0301063 小时前
并发编程二
java·开发语言
雪度娃娃3 小时前
转向现代C++——优先选用限定作用域的枚举型别,而非不限作用域的枚举型别
java·jvm·c++
不是光头 强3 小时前
Java 后端实战进阶:从踩坑到架构的系统化笔记
java·笔记·架构
ID_180079054733 小时前
企业级淘宝评论 API最简说明,JSON 返回示例
java·服务器·前端
Plan-C-3 小时前
二叉树的遍历
java·数据结构·算法
历程里程碑3 小时前
54 深入解析poll多路复用技术
java·linux·服务器·开发语言·前端·数据结构·c++
无限进步_3 小时前
【C++】可变参数模板与emplace系列
java·c++·算法