网站Logo 苏叶的belog

Spring StateMechaine

wdadwa
6
2026-02-24

一,什么是状态机?

状态机是一种行为模型,它由状态事件转换动作组成。

  • 状态:对象在某个时刻所处的模式(如:订单的“待支付”、“已支付”)。
  • 事件:触发状态变化的外部或内部动作(如:用户“支付”操作)。
  • 转换:从一个状态到另一个状态的路径(如:从“待支付”到“已支付”)。
  • 动作:状态转换时执行的操作(如:支付成功后发送短信通知)。

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 状态定义配置

状态定义包括初始状态、所有状态、结束状态、历史状态等。

  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() 定义历史状态,用于嵌套状态中记住子状态。

  2. 状态别名和状态类

    除了枚举,还可以使用 State<States, Events> 对象,并设置别名:

    states
        .withStates()
            .initial(States.STATE1)
            .state(States.STATE1, "待支付")   // 给状态设置别名
            .state(States.STATE2, "已支付");
    

4.2 转换配置

转换定义了状态之间如何流动。Spring StateMachine 支持多种转换类型:外部转换内部转换选择转换连接转换等。

  1. 外部转换

    外部转换表示从源状态到目标状态的切换,通常由事件触发。

    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);
    
  2. 内部转换

    内部转换不改变状态,但可以执行动作。通常用于响应事件但不离开当前状态。

    transitions
        .withInternal()
            .source(States.STATE1)
            .event(Events.INTERNAL_EVENT)
            .action(internalAction());
    
  3. 选择转换

    选择转换允许根据条件动态决定目标状态。它没有特定事件,而是在进入选择状态后自动评估。

    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
    

    选择状态需要先定义在状态集中,通常作为伪状态。

  4. 连接转换

    连接转换类似于选择,但更复杂,可以连接多个转换路径。

    transitions
        .withJunction()
            .source(States.JUNCTION)
            .first(States.STATE1, guard1())
            .then(States.STATE2, guard2())
            .last(States.STATE3);
    
  5. 分叉与合并(用于并行状态)

    • withFork():从一个源状态分叉到多个并行区域的目标状态。
    • withJoin():从多个源状态合并到一个目标状态,通常用于并行状态同步。

4.3 动作

动作是状态转换时执行的业务逻辑。定义方式多样:

  1. 使用 Lambda 表达式

    .action(context -> {
        // 获取事件、消息头、状态机等
        Message<Events> message = context.getMessage();
        System.out.println("执行动作,事件:" + message.getPayload());
    })
    
  2. 实现 Action 接口

    public class MyAction implements Action<States, Events> {
        @Override
        public void execute(StateContext<States, Events> context) {
            // 业务逻辑
        }
    }
    

    并在配置中引用:

    .action(new MyAction())
    
  3. 声明为 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 监听器

监听器可以监听状态机生命周期中的各种事件,如状态变化、转换、启动停止等。

  1. 实现监听器

    @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);
        }
    }
    
  2. 注册监听器

    在配置类中:

    @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 阶段,有两件事情要并行进行:

  1. 物流流程
    • WAIT_DELIVER
    • DELIVERING
    • RECEIVED
  2. 发票流程
    • WAIT_INVOICE
    • INVOICING
    • INVOICE_DONE

这两个流程:

  1. 互不影响
  2. 同时推进
  3. 但都属于 PROCESSING 阶段

结构图

PROCESSING
   ├── [Region1] 物流
   │       WAIT_DELIVER → DELIVERING → RECEIVED
   │
   └── [Region2] 发票
           WAIT_INVOICE → INVOICING → DONE

当进入 PROCESSING 时:

  • 两个 region 同时启动
  • 各自进入自己的 initial 子状态

Spring 配置示例

  1. 定义父状态

    states
        .withStates()
            .initial(OrderStates.WAIT_PAY)
            .state(OrderStates.PROCESSING)
            .end(OrderStates.FINISH);
    
  2. 定义第一个 Region(物流)

    .and()
    .withStates()
        .parent(OrderStates.PROCESSING)
        .region("logistics")
        .initial(OrderStates.WAIT_DELIVER)
        .state(OrderStates.WAIT_DELIVER)
        .state(OrderStates.DELIVERING)
        .end(OrderStates.RECEIVED);
    
    
  3. 定义第二个 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

现在发生异常,比如:暂停处理(挂起),然后再恢复。

如果没有历史状态:

  1. 再进入 PROCESSING
  2. 会回到 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 打日志
        ↓
状态改变
        ↓
持久化数据库
动物装饰