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

一、状态机核心概念与适用场景
状态机由三个核心要素组成:状态(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