一,什么是状态机?
状态机是一种行为模型,它由状态、事件、转换和动作组成。
- 状态:对象在某个时刻所处的模式(如:订单的“待支付”、“已支付”)。
- 事件:触发状态变化的外部或内部动作(如:用户“支付”操作)。
- 转换:从一个状态到另一个状态的路径(如:从“待支付”到“已支付”)。
- 动作:状态转换时执行的操作(如:支付成功后发送短信通知)。
Spring StateMachine 是 Spring 生态中的一个项目,它提供了强大的状态机实现,能够很好地与 Spring 应用集成。
二,核心概念
在 Spring StateMachine 中,我们需要了解几个核心接口和类:
| 概念 | 说明 |
|---|---|
State | 表示一个状态,通常用枚举定义 |
Event | 表示一个事件,通常用枚举定义 |
Transition | 定义了从源状态到目标状态的映射,以及触发的事件 |
Action | 在转换发生时执行的业务逻辑 |
StateMachine | 状态机实例,管理状态和转换 |
StateMachineConfigurer | 用于配置状态机的构建器 |
三,门的开关状态机示例
3.1环境准备
创建一个 Spring Boot 项目(以 Maven 为例),添加依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 这个是状态机-->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
3.2 定义状态和事件枚举
package com.example.door;
public enum DoorStates {
CLOSED,
OPEN
}
public enum DoorEvents {
OPEN,
CLOSE
}
3.3 配置状态机(重点)
创建一个配置类,继承 StateMachineConfigurerAdapter,并重写三个配置方法:states(定义状态)、transitions(定义转换)、configuration(配置状态机全局属性)。
package com.example.door;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
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 java.util.EnumSet;
@Configuration
@EnableStateMachine // 启用状态机,并创建一个默认的状态机 Bean
public class DoorStateMachineConfig extends StateMachineConfigurerAdapter<DoorStates, DoorEvents> {
@Override
public void configure(StateMachineStateConfigurer<DoorStates, DoorEvents> states) throws Exception {
states
.withStates()
.initial(DoorStates.CLOSED) // 初始状态
.states(EnumSet.allOf(DoorStates.class)); // 所有状态
}
@Override
public void configure(StateMachineTransitionConfigurer<DoorStates, DoorEvents> transitions) throws Exception {
transitions
.withExternal()
.source(DoorStates.CLOSED).target(DoorStates.OPEN)
.event(DoorEvents.OPEN) // 当在 CLOSED 状态收到 OPEN 事件时,转换到 OPEN
.and()
.withExternal()
.source(DoorStates.OPEN).target(DoorStates.CLOSED)
.event(DoorEvents.CLOSE); // 当在 OPEN 状态收到 CLOSE 事件时,转换到 CLOSED
}
@Override
public void configure(StateMachineConfigurationConfigurer<DoorStates, DoorEvents> config) throws Exception {
config
.withConfiguration()
.autoStartup(true) // 让状态机在应用启动时自动启动(可选)
.machineId("doorMachine"); // 给状态机一个 ID,便于区分
}
}
说明:
@EnableStateMachine会创建一个默认的状态机实例,类型为StateMachine<DoorStates, DoorEvents>,可以直接@Autowired使用。autoStartup(true)确保状态机在 Spring 容器启动时自动调用start()方法,进入初始状态。如果不设置,则需要手动调用start()。machineId可以为状态机指定唯一标识,便于监控和管理。
3.4 使用状态机
创建一个 Service,注入状态机,并封装发送事件的方法。
package com.example.door;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Service
public class DoorService {
@Autowired
private StateMachine<DoorStates, DoorEvents> stateMachine;
/**
* 可选:确保状态机已启动
*/
@PostConstruct
public void init() {
// 如果配置中没有设置 autoStartup(true),可以在这里手动启动
if (!stateMachine.isRunning()) {
stateMachine.start();
}
}
/**
* 开门
* @return 事件是否被接受(如果当前状态不允许该事件,返回 false)
*/
public boolean open() {
return stateMachine.sendEvent(DoorEvents.OPEN);
}
/**
* 关门
*/
public boolean close() {
return stateMachine.sendEvent(DoorEvents.CLOSE);
}
/**
* 获取当前状态
*/
public DoorStates getCurrentState() {
return stateMachine.getState().getId();
}
}
重要:
- 状态机必须调用
start()才能进入初始状态。如果配置中设置了autoStartup(true),Spring 会自动启动;否则你需要在第一次使用前调用start()。 sendEvent(event)返回boolean表示事件是否被当前状态接受并成功触发转换。如果事件不适用(例如在CLOSED状态发送CLOSE事件),将返回false,状态机状态不变。
3.5 测试
package com.example.door;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/door")
public class DoorController {
@Autowired
private DoorService doorService;
@PostMapping("/open")
public String open() {
boolean accepted = doorService.open();
return accepted ? "开门成功,当前状态:" + doorService.getCurrentState()
: "开门事件被拒绝,当前状态:" + doorService.getCurrentState();
}
@PostMapping("/close")
public String close() {
boolean accepted = doorService.close();
return accepted ? "关门成功,当前状态:" + doorService.getCurrentState()
: "关门事件被拒绝,当前状态:" + doorService.getCurrentState();
}
@GetMapping("/state")
public String state() {
return "当前状态:" + doorService.getCurrentState();
}
}
四,状态机配置概览
Spring StateMachine 的配置主要通过继承 StateMachineConfigurerAdapter 并重写其三个 configure 方法来实现
@Configuration
@EnableStateMachine
public class MyStateMachineConfig extends StateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
// 配置状态定义
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
// 配置状态转换
}
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
// 配置状态机全局设置
}
}
注意:
@EnableStateMachine创建一个单例状态机 Bean,适用于单一流程。- 如需多实例(如每个订单一个状态机),使用
@EnableStateMachineFactory。
4.1 状态定义配置
状态定义包括初始状态、所有状态、结束状态、历史状态等。
-
基本状态定义
@Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.STATE1) // 初始状态 .states(EnumSet.allOf(States.class)) // 所有状态(自动添加) .end(States.END) // 结束状态(可多个) .history(States.HISTORY, History.SHALLOW); // 历史状态(用于嵌套状态) }-
initial()必须指定。 -
states()可以添加单个或多个状态。 -
end()标记哪些状态是结束状态,进入后状态机完成。 -
history()定义历史状态,用于嵌套状态中记住子状态。
-
-
状态别名和状态类
除了枚举,还可以使用
State<States, Events>对象,并设置别名:states .withStates() .initial(States.STATE1) .state(States.STATE1, "待支付") // 给状态设置别名 .state(States.STATE2, "已支付");
4.2 转换配置
转换定义了状态之间如何流动。Spring StateMachine 支持多种转换类型:外部转换、内部转换、选择转换、连接转换等。
-
外部转换
外部转换表示从源状态到目标状态的切换,通常由事件触发。
transitions .withExternal() .source(States.STATE1) .target(States.STATE2) .event(Events.EVENT1) .action(action()) // 转换时执行的动作 .guard(guard()) // 守卫条件,决定是否允许转换 .and() .withExternal() .source(States.STATE2) .target(States.STATE3) .event(Events.EVENT2); -
内部转换
内部转换不改变状态,但可以执行动作。通常用于响应事件但不离开当前状态。
transitions .withInternal() .source(States.STATE1) .event(Events.INTERNAL_EVENT) .action(internalAction()); -
选择转换
选择转换允许根据条件动态决定目标状态。它没有特定事件,而是在进入选择状态后自动评估。
transitions .withChoice() .source(States.CHOICE_STATE) .first(States.STATE_A, guardA()) // 如果 guardA 为 true,转到 STATE_A .then(States.STATE_B, guardB()) // 否则如果 guardB 为 true,转到 STATE_B .last(States.STATE_C); // 否则转到 STATE_C选择状态需要先定义在状态集中,通常作为伪状态。
-
连接转换
连接转换类似于选择,但更复杂,可以连接多个转换路径。
transitions .withJunction() .source(States.JUNCTION) .first(States.STATE1, guard1()) .then(States.STATE2, guard2()) .last(States.STATE3); -
分叉与合并(用于并行状态)
withFork():从一个源状态分叉到多个并行区域的目标状态。withJoin():从多个源状态合并到一个目标状态,通常用于并行状态同步。
4.3 动作
动作是状态转换时执行的业务逻辑。定义方式多样:
-
使用 Lambda 表达式
.action(context -> { // 获取事件、消息头、状态机等 Message<Events> message = context.getMessage(); System.out.println("执行动作,事件:" + message.getPayload()); }) -
实现 Action 接口
public class MyAction implements Action<States, Events> { @Override public void execute(StateContext<States, Events> context) { // 业务逻辑 } }并在配置中引用:
.action(new MyAction()) -
声明为 Bean 并注入
@Bean public Action<States, Events> myAction() { return context -> { ... }; }然后在转换中通过
@Autowired或直接调用方法引用.action(myAction()) // 如果是同一个配置类中的 @Bean 方法
动作的执行时机
默认动作在转换过程中执行。可以通过 StateContext 判断是转换前还是转换后(通常没有明确的前后区分,动作本身就是转换的一部分)。如果需要在转换前后执行不同逻辑,可以使用 StateMachineListener 监听事件。
4.4 守卫
守卫决定转换是否可以被触发。它基于当前上下文返回 true(允许)或 false(拒绝)。
@Bean
public Guard<States, Events> myGuard() {
return context -> {
// 检查条件,例如从消息头获取参数
Integer amount = (Integer) context.getMessageHeader("amount");
return amount != null && amount > 100;
};
}
守卫通常在转换之前评估,如果返回 false,则转换不会发生,事件被忽略。
4.5 监听器
监听器可以监听状态机生命周期中的各种事件,如状态变化、转换、启动停止等。
-
实现监听器
@Component public class MyStateMachineListener extends StateMachineListenerAdapter<States, Events> { @Override public void stateChanged(State<States, Events> from, State<States, Events> to) { System.out.println("状态从 " + from + " 变为 " + to); } @Override public void eventNotAccepted(Message<Events> event) { System.out.println("事件未被接受:" + event.getPayload()); } @Override public void transition(Transition<States, Events> transition) { System.out.println("转换执行:" + transition); } } -
注册监听器
在配置类中:
@Autowired private MyStateMachineListener listener; @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withConfiguration() .listener(listener); }
4.6 全局配置
除了监听器,还可以配置以下内容:
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
config
.withConfiguration()
.machineId("myMachine") // 状态机标识
.autoStartup(true) // 自动启动
.taskExecutor(taskExecutor()) // 异步执行器(用于异步动作)
.beanFactory(beanFactory) // 设置 Bean 工厂
.verifierEnabled(true); // 启动时验证配置
}
autoStartup:应用启动时自动调用start()。taskExecutor:如果动作是异步的,可以指定线程池。verifierEnabled:启动时检查配置是否有误(如缺少状态、事件重复等)。
五,高级状态机特性
5.1 嵌套状态
嵌套状态允许一个状态包含多个子状态,子状态继承父状态的转换。
定义嵌套状态:
states
.withStates()
.initial(STATE1)
.state(PARENT)
.and()
.withStates()
.parent(PARENT)
.initial(CHILD1)
.state(CHILD1)
.state(CHILD2)
.end(CHILD_END);
这表示:当状态机进入 PARENT 时,会自动进入它的 initial 子状态
STATE1
↓
PARENT
├── CHILD1 (初始子状态)
├── CHILD2
└── CHILD_END
我们设计一个复杂订单流程:
订单主状态:
- WAIT_PAY
- PROCESSING
- FINISHED
- CANCELLED
但 PROCESSING 其实还可以细分:
PROCESSING
├── WAIT_DELIVER
├── DELIVERING
└── RECEIVED
这就是嵌套状态最适合的场景。
结构如下
WAIT_PAY
↓ 支付
PROCESSING
├── WAIT_DELIVER
├── DELIVERING
└── RECEIVED
↓
FINISHED
转换规则:从外部进入父状态时,会进入其初始子状态;从父状态内部事件可以触发子状态间的转换;也可以定义从父状态到外部的转换。
5.2 并行状态
并行状态 = 一个状态下面,同时运行多个“子状态机”
和嵌套状态不同:
- 嵌套状态:同一时间只会有一个子状态活跃
- 并行状态:同一时间可以有多个子状态同时活跃
用订单举例:假设一个订单在 PROCESSING 阶段,有两件事情要并行进行:
- 物流流程
- WAIT_DELIVER
- DELIVERING
- RECEIVED
- 发票流程
- WAIT_INVOICE
- INVOICING
- INVOICE_DONE
这两个流程:
- 互不影响
- 同时推进
- 但都属于 PROCESSING 阶段
结构图
PROCESSING
├── [Region1] 物流
│ WAIT_DELIVER → DELIVERING → RECEIVED
│
└── [Region2] 发票
WAIT_INVOICE → INVOICING → DONE
当进入 PROCESSING 时:
- 两个 region 同时启动
- 各自进入自己的 initial 子状态
Spring 配置示例
-
定义父状态
states .withStates() .initial(OrderStates.WAIT_PAY) .state(OrderStates.PROCESSING) .end(OrderStates.FINISH); -
定义第一个 Region(物流)
.and() .withStates() .parent(OrderStates.PROCESSING) .region("logistics") .initial(OrderStates.WAIT_DELIVER) .state(OrderStates.WAIT_DELIVER) .state(OrderStates.DELIVERING) .end(OrderStates.RECEIVED); -
定义第二个 Region(发票)
.and() .withStates() .parent(OrderStates.PROCESSING) .region("invoice") .initial(OrderStates.WAIT_INVOICE) .state(OrderStates.WAIT_INVOICE) .state(OrderStates.INVOICING) .end(OrderStates.INVOICE_DONE);
关键点:
.region("xxx")
每个 region 是一个独立子状态机。
什么时候父状态算完成?:父状态 PROCESSING 只有在:所有 region 都到达 end 状态,才算完成。
比如:
- 物流 → RECEIVED
- 发票 → INVOICE_DONE
这时可以自动 transition 到:FINISH
5.3 历史状态
历史状态 = 记住上一次离开父状态时的子状态
它解决的问题是:
- 当重新进入父状态时,是回到初始子状态?还是回到之前离开的那个子状态?
不使用历史状态会发生什么
假设订单有一个 PROCESSING 阶段:
PROCESSING
├── WAIT_DELIVER
├── DELIVERING
└── RECEIVED
流程:
WAIT_PAY
↓ 支付
PROCESSING → WAIT_DELIVER
↓ 发货
DELIVERING
现在发生异常,比如:暂停处理(挂起),然后再恢复。
如果没有历史状态:
- 再进入 PROCESSING
- 会回到 initial 子状态,也就是 WAIT_DELIVER
但真实业务应该是:回到 DELIVERING ,这就是历史状态的意义。
Spring 配置方式:
.withStates()
.parent(OrderStates.PROCESSING)
// 这里记录的是浅历史
.history(OrderStates.PROCESSING_HISTORY, History.SHALLOW)
.initial(OrderStates.WAIT_DELIVER)
.state(OrderStates.WAIT_DELIVER)
.state(OrderStates.DELIVERING)
.state(OrderStates.RECEIVED);
作用:记住最近一次的“直接子状态”
深历史
如果有多层嵌套:
PROCESSING
├── SHIPPING
│ ├── PACKING
│ ├── DELIVERING
│
└── INVOICE
深历史会记住:
PROCESSING
SHIPPING
DELIVERING
整个路径。
配置:
.history(OrderStates.PROCESSING_HISTORY, History.DEEP)
5.4 状态机工厂(多实例)
使用 @EnableStateMachineFactory 替代 @EnableStateMachine,然后注入 StateMachineFactory:
@Configuration
@EnableStateMachineFactory
public class OrderStateMachineFactoryConfig extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {
// 配置同上,但会创建一个工厂 Bean
}
@Service
public class OrderService {
@Autowired
private StateMachineFactory<OrderStates, OrderEvents> factory;
public StateMachine<OrderStates, OrderEvents> createForOrder(String orderId) {
StateMachine<OrderStates, OrderEvents> sm = factory.getStateMachine(orderId);
sm.start();
return sm;
}
}
每个状态机实例独立,适合订单等场景。
5.5 持久化
持久化允许保存状态机上下文,以便恢复。需要实现 StateMachinePersist 接口。
定义持久化实现
@Component
public class InMemoryStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, String> {
private Map<String, StateMachineContext<OrderStates, OrderEvents>> store = new ConcurrentHashMap<>();
@Override
public void write(StateMachineContext<OrderStates, OrderEvents> context, String id) throws Exception {
store.put(id, context);
}
@Override
public StateMachineContext<OrderStates, OrderEvents> read(String id) throws Exception {
return store.get(id);
}
}
注意:
- 保存上下文需要在状态机停止或在安全点进行,通常事件处理后保存。
- 持久化完毕后需要在配置类里面配置一个持久化Bean
@Bean
public StateMachinePersister<OrderStates, OrderEvents, String> persister(
StateMachinePersist<OrderStates, OrderEvents, String> persist) {
return new DefaultStateMachinePersister<>(persist);
}
六,完整示例:订单状态机
6.1 业务场景设计
订单流程
WAIT_PAY --(PAY)--> WAIT_DELIVER --(DELIVER)--> FINISH
\
--(CANCEL)--> CANCELLED
完整代码结构
order/
├── enums
├── config
├── action
├── guard
├── listener
├── persist
├── dao
└── service
6.2 状态 & 事件定义
@Getter
@AllArgsConstructor
public enum OrderEvents {
PAY("0", "支付"),
DELIVER("1", "发货"),
CANCEL("2", "取消");
private final String value;
private final String desc;
public static OrderEvents fromValue(String value) {
for (OrderEvents event : values()) {
if (event.getValue().equals(value)) {
return event;
}
}
throw new IllegalArgumentException("非法事件值: " + value);
}
}
@Getter
@AllArgsConstructor
public enum OrderStates {
WAIT_PAY("0", "待支付"),
WAIT_DELIVER("1", "待发货"),
FINISH("2", "已完成"),
CANCELLED("3", "已取消");
private final String value;
private final String desc;
public static OrderStates fromValue(String value) {
for (OrderStates state : values()) {
if (state.getValue().equals(value)) {
return state;
}
}
throw new IllegalArgumentException("非法状态值: " + value);
}
}
6.3 模拟数据库 DAO
@Repository
public class OrderDao {
private Map<String, String> database = new ConcurrentHashMap<>();
public void saveState(String orderId, String state) {
System.out.println("【DB】保存订单状态:" + orderId + " -> " + state);
database.put(orderId, state);
}
public String getState(String orderId) {
System.out.println("【DB】查询订单状态:" + orderId);
return database.get(orderId);
}
public void updateOrderInfo(String orderId) {
System.out.println("【DB】更新订单业务数据:" + orderId);
}
}
6.4 Guard(库存校验示例)
@Component
public class PayGuard implements Guard<OrderStates, OrderEvents> {
@Override
public boolean evaluate(StateContext<OrderStates, OrderEvents> context) {
Integer stock = (Integer) context.getMessageHeader("stock");
System.out.println("【Guard】检查库存:" + stock);
return stock != null && stock > 0;
}
}
6.5 Action(支付 & 发货)
支付动作
@Component
public class PayAction implements Action<OrderStates, OrderEvents> {
@Autowired
private OrderDao orderDao;
@Override
public void execute(StateContext<OrderStates, OrderEvents> context) {
String orderId = (String) context.getMessageHeader("orderId");
System.out.println("【Action】执行支付逻辑:" + orderId);
orderDao.updateOrderInfo(orderId);
}
}
发货动作
@Component
public class DeliverAction implements Action<OrderStates, OrderEvents> {
@Override
public void execute(StateContext<OrderStates, OrderEvents> context) {
String orderId = (String) context.getMessageHeader("orderId");
System.out.println("【Action】执行发货逻辑:" + orderId);
}
}
6.6 Listener
@Component
public class OrderStateMachineListener
extends StateMachineListenerAdapter<OrderStates, OrderEvents> {
@Override
public void stateChanged(State<OrderStates, OrderEvents> from,
State<OrderStates, OrderEvents> to) {
System.out.println("【Listener】状态变化:" +
(from == null ? "无" : from.getId()) +
" -> " + to.getId());
}
@Override
public void eventNotAccepted(Message<OrderEvents> event) {
System.out.println("【Listener】事件未被接受:" + event.getPayload());
}
}
6.7 持久化(Persister)
@Component
public class OrderPersist implements StateMachinePersist<OrderStates, OrderEvents, String> {
@Autowired
private OrderDao orderDao;
@Override
public void write(StateMachineContext<OrderStates, OrderEvents> context, String orderId) throws Exception {
orderDao.saveState(orderId, context.getState().getValue());
}
@Override
public StateMachineContext<OrderStates, OrderEvents> read(String orderId) throws Exception {
String state = orderDao.getState(orderId);
if (state == null) {
return null;
}
return new DefaultStateMachineContext<>(
OrderStates.fromValue(state),
null, null, null
);
}
}
6.8 状态机完整配置类
package com.normaling.springdemo.stateMechaineDemo.demo2.config;
import com.normaling.springdemo.stateMechaineDemo.demo2.action.DeliverAction;
import com.normaling.springdemo.stateMechaineDemo.demo2.action.PayAction;
import com.normaling.springdemo.stateMechaineDemo.demo2.enums.OrderEvents;
import com.normaling.springdemo.stateMechaineDemo.demo2.enums.OrderStates;
import com.normaling.springdemo.stateMechaineDemo.demo2.guard.PayGuard;
import com.normaling.springdemo.stateMechaineDemo.demo2.listener.OrderStateMachineListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateMachinePersist;
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.persist.DefaultStateMachinePersister;
import org.springframework.statemachine.persist.StateMachinePersister;
import java.util.EnumSet;
/**
* 订单状态机配置类
*
* 说明:
* 1. 使用 @EnableStateMachineFactory 表示创建的是“状态机工厂”
* 而不是单例状态机。
* 2. 每个订单都会通过工厂创建一个独立的状态机实例。
* 3. 适用于订单、审批流等多实例场景。
*/
@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig
extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {
/**
* 支付动作(状态流转时执行)
*/
@Autowired
private PayAction payAction;
/**
* 发货动作
*/
@Autowired
private DeliverAction deliverAction;
/**
* 支付前的守卫(用于库存校验等)
*/
@Autowired
private PayGuard payGuard;
/**
* 状态机监听器(只负责打日志)
*/
@Autowired
private OrderStateMachineListener listener;
/**
* ===============================
* 一、状态定义配置
* ===============================
*
* 定义:
* - 初始状态
* - 所有状态
* - 结束状态
*/
@Override
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states)
throws Exception {
states.withStates()
// 初始状态(状态机 start() 后进入的状态)
.initial(OrderStates.WAIT_PAY)
// 注册所有状态(从枚举中自动加载)
.states(EnumSet.allOf(OrderStates.class))
// 定义结束状态
// 一旦进入 FINISH 或 CANCELLED
// 状态机流程结束
.end(OrderStates.FINISH)
.end(OrderStates.CANCELLED);
}
/**
* ===============================
* 二、状态流转规则配置
* ===============================
*
* 规则模型:
* 当前状态 + 事件 +(Guard条件) = 目标状态 + 执行动作
*/
@Override
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions)
throws Exception {
transitions
/**
* WAIT_PAY + PAY → WAIT_DELIVER
*
* 条件:
* 1. 必须通过 payGuard 校验(例如库存大于0)
* 2. 执行 payAction(更新订单信息)
*/
.withExternal()
.source(OrderStates.WAIT_PAY) // 源状态
.target(OrderStates.WAIT_DELIVER) // 目标状态
.event(OrderEvents.PAY) // 触发事件
.guard(payGuard) // 守卫(条件判断)
.action(payAction) // 状态流转时执行的业务逻辑
.and()
/**
* WAIT_DELIVER + DELIVER → FINISH
*
* 发货完成,订单结束
*/
.withExternal()
.source(OrderStates.WAIT_DELIVER)
.target(OrderStates.FINISH)
.event(OrderEvents.DELIVER)
.action(deliverAction)
.and()
/**
* WAIT_PAY + CANCEL → CANCELLED
*
* 订单未支付前可以取消
*/
.withExternal()
.source(OrderStates.WAIT_PAY)
.target(OrderStates.CANCELLED)
.event(OrderEvents.CANCEL);
}
/**
* ===============================
* 三、状态机全局配置
* ===============================
*
* 包含:
* - machineId
* - 是否自动启动
* - 监听器
*/
@Override
public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config)
throws Exception {
config.withConfiguration()
// 状态机ID(用于区分不同状态机)
.machineId("orderMachine")
// 不自动启动
// 使用工厂创建后,需要手动调用 start()
.autoStartup(false)
// 注册监听器
// 用于监听状态变化、事件拒绝等
.listener(listener);
}
// 配置一个持久化bean
@Bean
public StateMachinePersister<OrderStates, OrderEvents, String> persister(
StateMachinePersist<OrderStates, OrderEvents, String> persist) {
return new DefaultStateMachinePersister<>(persist);
}
}
6.9 Service
@Service
public class OrderService {
@Autowired
private StateMachineFactory<OrderStates, OrderEvents> factory;
@Autowired
private StateMachinePersister<OrderStates, OrderEvents, String> persister;
@Transactional
public void sendEvent(String orderId,
OrderEvents event,
Integer stock) throws Exception {
// 1. 创建状态机
StateMachine<OrderStates, OrderEvents> sm =
factory.getStateMachine(orderId);
sm.stop();
// 2. 恢复状态
persister.restore(sm, orderId);
sm.start();
// 3. 构造事件(携带消息头)
Message<OrderEvents> message =
MessageBuilder.withPayload(event)
.setHeader("orderId", orderId)
.setHeader("stock", stock)
.build();
// 4. 发送事件
boolean accepted = sm.sendEvent(message);
if (!accepted) {
throw new RuntimeException("事件不允许触发");
}
// 5. 持久化
persister.persist(sm, orderId);
System.out.println("【Service】当前状态:" + sm.getState().getId());
}
}
6.10 Controller
package com.normaling.springdemo.stateMechaineDemo.demo2.controller;
import com.normaling.springdemo.stateMechaineDemo.demo2.enums.OrderEvents;
import com.normaling.springdemo.stateMechaineDemo.demo2.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 订单控制器
*
* 作用:
* 1. 接收前端请求
* 2. 触发状态机事件
* 3. 返回当前状态
*
* 测试建议使用 Postman 或 curl
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 支付订单
*
* 示例:
* POST /order/pay?orderId=1001&stock=10
*
* stock 用于 Guard 校验库存
*/
@PostMapping("/pay")
public String pay(@RequestParam String orderId,
@RequestParam Integer stock) {
try {
orderService.sendEvent(orderId, OrderEvents.PAY, stock);
return "支付成功";
} catch (Exception e) {
return "支付失败:" + e.getMessage();
}
}
/**
* 发货
*
* 示例:
* POST /order/deliver?orderId=1001
*/
@PostMapping("/deliver")
public String deliver(@RequestParam String orderId) {
try {
orderService.sendEvent(orderId, OrderEvents.DELIVER, null);
return "发货成功";
} catch (Exception e) {
return "发货失败:" + e.getMessage();
}
}
/**
* 取消订单
*
* 示例:
* POST /order/cancel?orderId=1001
*/
@PostMapping("/cancel")
public String cancel(@RequestParam String orderId) {
try {
orderService.sendEvent(orderId, OrderEvents.CANCEL, null);
return "取消成功";
} catch (Exception e) {
return "取消失败:" + e.getMessage();
}
}
}
6.11 完整执行流程图
Controller → Service (@Transactional)
↓
创建状态机
↓
恢复数据库状态
↓
发送事件(带 header)
↓
Guard 校验
↓
Action 执行业务
↓
Listener 打日志
↓
状态改变
↓
持久化数据库