导读:本期聚焦于李修然创作的《Spring Boot 如何整合 Spring Statemachine 实现订单状态机?》,敬请观看详情。订单从创建到支付、发货、完成,中间要经历好几个状态流转,如果用一堆 if else 去判断状态合法性,代码很快就会变成一团乱麻。Spring Statemachine 是 Spring 官方提供的状态机框架,通过声明式的方式定义状态和事件,让状态流转逻辑变得清晰可控。本文将介绍状态机的基本概念,演示 Spring Boot 项目中如何引入和配置 Spring Statemachine,包括状态与事件的定义、状态机的构建、事件触发的动作监听,并结合订单流转场景给出完整代码示例,最后分析状态机在持久化和分布式场景下的进阶用法与常见坑,帮助你写出更健壮的状态管理代码。

在业务系统里,状态流转无处不在:订单有待支付、已支付、已发货、已完成、已取消,审批工单有草稿、审批中、已通过、已驳回。如果状态数量一多,散落在各个业务方法里的 if else 判断就会越来越难维护,漏判一个状态还容易产生脏数据。状态机(State Machine)是解决这类问题的经典方案,把状态、事件、流转规则集中起来统一管理。Spring Statemachine 是 Spring 官方团队推出的状态机框架,和 Spring Boot 无缝集成,本文以订单流转为例,完整演示整合过程。

Spring Boot 如何整合 Spring Statemachine 实现订单状态机?

一、状态机核心概念与适用场景

状态机由三个核心要素组成:状态(State)、事件(Event)和转换(Transition)。状态描述对象在某一时刻的形态,事件是触发状态变化的外部动作,转换则定义了“在什么状态下收到什么事件,就迁移到哪个新状态”。比如订单处于“待支付”状态时收到“支付”事件,就迁移到“已支付”状态;如果处于“已发货”状态再收到“支付”事件,就是非法操作,状态机会直接拒绝。

Spring Statemachine 在这基础上还扩展了动作(Action)、守卫(Guard)等概念。Action 是状态转换时执行的逻辑,比如发消息通知、记日志;Guard 是转换前的条件校验,返回 false 则阻止本次转换。这些概念组合起来,几乎能覆盖所有状态流转场景。

需要注意的是,状态机适合“状态数量有限、流转规则明确”的场景。如果业务只是简单的一两个状态开关,直接用一个字段加枚举就够了,强行上状态机反而增加复杂度。而当状态超过四五个、流转分支变多、多人协作时状态判断开始到处复制粘贴,这时候引入状态机的收益才会明显。

二、Spring Boot 整合步骤与基础配置

先引入依赖,在 pom.xml 中加入 spring-statemachine-core:

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>3.2.1</version>
</dependency>

接下来定义状态枚举和事件枚举。这两个枚举是状态机的骨架,建议放在独立的包里,命名清晰:

public enum OrderStatus {
    WAIT_PAY,      // 待支付
    PAID,          // 已支付
    SHIPPED,       // 已发货
    FINISHED,      // 已完成
    CANCELLED      // 已取消
}

public enum OrderEvent {
    PAY,           // 支付
    SHIP,          // 发货
    CONFIRM,       // 确认收货
    CANCEL         // 取消订单
}

然后通过配置类构建状态机。Spring Statemachine 提供了 StateMachineBuilderFactory 或者继承 StateMachineConfigurerAdapter 的方式,这里用推荐的配置适配器写法,通过注解开启状态机功能:

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig
        extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {

    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states)
            throws Exception {
        states.withStates()
              .initial(OrderStatus.WAIT_PAY)
              .states(EnumSet.allOf(OrderStatus.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(OrderStatus.WAIT_PAY).target(OrderStatus.PAID)
                .event(OrderEvent.PAY)
                .action(payAction())
            .and()
            .withExternal()
                .source(OrderStatus.PAID).target(OrderStatus.SHIPPED)
                .event(OrderEvent.SHIP)
            .and()
            .withExternal()
                .source(OrderStatus.SHIPPED).target(OrderStatus.FINISHED)
                .event(OrderEvent.CONFIRM)
            .and()
            .withExternal()
                .source(OrderStatus.WAIT_PAY).target(OrderStatus.CANCELLED)
                .event(OrderEvent.CANCEL);
    }

    @Bean
    public Action<OrderStatus, OrderEvent> payAction() {
        return context -> {
            System.out.println("订单支付成功,当前状态:" + context.getTarget().getId());
        };
    }
}

配置分两块:configure(states) 声明所有状态和初始状态,configure(transitions) 声明每一条合法的流转路径。注意这里没有定义“已发货状态收到取消事件”的规则,所以这种非法请求会被状态机自动拦截,这正是集中管理流转规则的价值所在。

三、事件触发与监听器实战

配置完成后,注入 StateMachine 就可以发送事件了。发送事件有两种方式:调用 sendEvent 方法同步触发,或者通过事件监听器异步响应。先看最直接的用法:

@Service
public class OrderService {

    @Autowired
    private StateMachine<OrderStatus, OrderEvent> stateMachine;

    public boolean changeState(OrderEvent event) {
        stateMachine.start();
        boolean accepted = stateMachine.sendEvent(event);
        if (!accepted) {
            throw new IllegalStateException("当前状态下不允许该操作:" + event);
        }
        return accepted;
    }
}

sendEvent 的返回值很重要,返回 true 表示事件被接受并成功流转,false 表示当前状态不接受该事件。业务代码据此给用户返回友好提示,而不是让非法状态悄悄溜过去。

除了 Action,更常用的还有监听器。通过 StateMachineListener 可以监听状态变化、转换完成、错误等事件,适合做统一的日志记录和消息通知:

@WithStateMachine
@Service
public class OrderStateMachineListener {

    @OnTransition(source = "WAIT_PAY", target = "PAID")
    public void payTransition() {
        System.out.println("监听到订单从待支付变为已支付,执行扣库存逻辑");
    }

    @OnTransition(target = "CANCELLED")
    public void cancelTransition() {
        System.out.println("监听到订单取消,执行退款逻辑");
    }
}

这种注解式监听比实现接口的写法简洁很多,@OnTransition 可以精确匹配 source 和 target,把不同流转对应的业务逻辑拆到不同方法里,可读性比一个巨型 switch 好得多。

四、多实例管理与持久化进阶

上面的写法只维护了一个单例状态机,但真实业务中每个订单都是独立的,各有各的状态。这就需要用到 StateMachineFactory,配合状态机 ID 为每个订单创建独立实例:

@Service
public class OrderStateMachineService {

    @Autowired
    private StateMachineFactory<OrderStatus, OrderEvent> factory;

    @Autowired
    private StateMachinePersister<OrderStatus, OrderEvent, String> persister;

    public void handleEvent(String orderId, OrderEvent event) throws Exception {
        StateMachine<OrderStatus, OrderEvent> sm = factory.getStateMachine(orderId);
        // 从数据库恢复该订单的状态
        persister.restore(sm, orderId);
        boolean ok = sm.sendEvent(event);
        if (ok) {
            // 流转成功后持久化新状态
            persister.persist(sm, orderId);
        }
    }
}

持久化靠 StateMachinePersister 实现,默认可以存到内存,生产环境建议接入 Redis 或数据库,把状态机的当前状态和上下文序列化保存。这样即使应用重启,订单的状态机也能从上次的位置继续工作。

最后提两个常见的坑。第一,状态机实例是有状态的,Web 应用中如果直接把单例 StateMachine 注入到 Controller 并发使用,会出现状态串号,务必用 Factory 按业务标识隔离。第二,分布式部署下多个节点同时操作同一订单,需要在持久化层加乐观锁或者分布式锁,否则可能出现两个节点基于同一个旧状态各自流转一次的情况。把这两个问题处理好,状态机方案才算真正落地。

Spring BootSpring Statemachine状态机修改时间:2026-09-12 11:28:38

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260912/55303.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。