Spring State Machine 入门教程:快速构建状态驱动应用 – wiki基地

Spring State Machine 入门教程:快速构建状态驱动应用

在复杂的业务场景中,我们常常需要管理对象的状态,并根据不同的状态执行不同的操作。状态模式是一种常用的设计模式,用于解决此类问题。Spring State Machine (SSM) 是一个强大的框架,它基于状态模式的思想,提供了丰富的功能来简化状态驱动应用程序的开发。本教程将深入探讨 Spring State Machine,并通过一个实际示例,帮助你快速构建状态驱动应用程序。

1. 什么是 Spring State Machine?

Spring State Machine 是一个 Spring 框架的扩展,它提供了一种声明式的方式来定义和管理状态机。它让你能够清晰地定义应用程序的状态、事件以及状态之间的转换,从而使代码更具可读性、可维护性和可测试性。

Spring State Machine 的核心概念包括:

  • 状态 (State): 状态机在某一时刻所处的状态,代表了对象的一种特定情况。例如,订单的状态可能包括“创建”、“已支付”、“已发货”、“已完成”等。

  • 事件 (Event): 触发状态转换的信号。例如,用户支付订单会触发“支付”事件,导致订单状态从“创建”转换为“已支付”。

  • 转换 (Transition): 从一个状态到另一个状态的路径,由事件触发。一个转换可以包含:

    • 源状态 (Source State): 转换开始的状态。
    • 目标状态 (Target State): 转换结束的状态。
    • 事件 (Event): 触发转换的事件。
    • Guard: 守卫条件,是一个布尔表达式,只有当守卫条件为真时,转换才能执行。
    • Action: 动作,是在转换执行时执行的业务逻辑。
  • 状态机 (StateMachine): 状态、事件和转换的集合,定义了对象的所有可能状态和状态之间的转换规则。

  • Context: 上下文,用于存储状态机运行过程中所需的数据,例如订单信息、用户信息等。

2. Spring State Machine 的优势

使用 Spring State Machine 有许多好处,包括:

  • 清晰的状态管理: 通过明确地定义状态和转换,可以清晰地了解应用程序的状态变化,避免状态混乱。
  • 代码可读性和可维护性: 将状态转换逻辑与业务逻辑分离,使代码更易于阅读和维护。
  • 易于测试: 可以将状态机作为一个独立的单元进行测试,确保状态转换的正确性。
  • 可扩展性: 通过添加新的状态、事件和转换,可以轻松地扩展状态机的功能。
  • 与 Spring 框架的无缝集成: Spring State Machine 可以与其他 Spring 框架组件(如 Spring Data、Spring Security)无缝集成,简化开发过程。
  • 支持多种持久化方案: 支持将状态机配置持久化到数据库、文件等,实现状态的恢复。

3. 搭建 Spring State Machine 开发环境

首先,你需要一个 Spring Boot 项目。可以使用 Spring Initializr (https://start.spring.io/) 创建一个基础项目。

然后,在 pom.xml 文件中添加 Spring State Machine 的依赖:

xml
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.2.0</version> <!-- 请使用最新版本 -->
</dependency>

4. 构建一个简单的订单状态机示例

让我们创建一个简单的订单状态机,包含以下状态:

  • CREATED: 订单已创建。
  • PAID: 订单已支付。
  • SHIPPED: 订单已发货。
  • COMPLETED: 订单已完成。
  • CANCELLED: 订单已取消

以及以下事件:

  • PAY: 支付订单。
  • SHIP: 发货。
  • COMPLETE: 完成订单。
  • CANCEL: 取消订单。

4.1 定义状态枚举和事件枚举

首先,定义两个枚举类,分别表示状态和事件:

java
public enum OrderState {
CREATED,
PAID,
SHIPPED,
COMPLETED,
CANCELLED
}

java
public enum OrderEvent {
PAY,
SHIP,
COMPLETE,
CANCEL,
}

4.2 创建状态机配置类

创建一个配置类来定义状态机的状态、事件和转换:

“`java
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

import java.util.EnumSet;

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter {

@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
    states.withStates()
            .initial(OrderState.CREATED)
            .states(EnumSet.allOf(OrderState.class)); //定义所有状态
}

@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
    transitions.withExternal()
            .source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
            .and()
            .withExternal()
            .source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
            .and()
            .withExternal()
            .source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.COMPLETE)
            .and()
            .withExternal()
            .source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
            .and()
            .withExternal()
            .source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL); // 允许在支付后取消
}

}
“`

  • @Configuration 注解表明这是一个配置类。
  • @EnableStateMachine 注解启用 Spring State Machine。
  • StateMachineConfigurerAdapter 是一个适配器类,用于配置状态机。
  • configure(StateMachineStateConfigurer states) 方法用于配置状态。
    • states.withStates() 开始配置状态。
    • initial(OrderState.CREATED) 设置初始状态为 CREATED
    • states(EnumSet.allOf(OrderState.class)) 定义所有状态。
  • configure(StateMachineTransitionConfigurer transitions) 方法用于配置转换。
    • transitions.withExternal() 开始配置外部转换(即源状态和目标状态在状态机外部)。
    • source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY) 定义从 CREATEDPAID 的转换,由 PAY 事件触发。
    • and() 用于连接多个转换配置。

4.3 添加 Guard 和 Action (可选)

Guard 可以控制转换是否执行,Action 可以执行一些业务逻辑。 让我们添加一个 Guard,只有当订单金额大于 0 时才能支付:

“`java
import org.springframework.context.annotation.Bean;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.guard.Guard;

// … 省略其他代码

@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
    transitions.withExternal()
            .source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
            .guard(orderAmountGuard()) // 添加 Guard
            .and()
            .withExternal()
            .source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
            .and()
            .withExternal()
            .source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.COMPLETE)
            .and()
            .withExternal()
            .source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
            .and()
            .withExternal()
            .source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL); // 允许在支付后取消
}


@Bean
public Guard<OrderState, OrderEvent> orderAmountGuard() {
    return context -> {
        Double amount = (Double) context.getExtendedState().getVariables().get("orderAmount");
        return amount != null && amount > 0;
    };
}

“`

  • orderAmountGuard() 方法创建了一个 Guard 实例。
  • context.getExtendedState().getVariables().get("orderAmount") 从状态机的上下文中获取订单金额。
  • 只有当订单金额大于 0 时,orderAmountGuard() 方法才返回 true,转换才能执行。

现在,让我们添加一个 Action,在支付成功后发送支付成功的通知:

“`java
import org.springframework.context.annotation.Bean;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
// … 省略其他代码

@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
    transitions.withExternal()
            .source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
            .guard(orderAmountGuard()) // 添加 Guard
            .action(paymentSuccessAction()) // 添加 Action
            .and()
            .withExternal()
            .source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
            .and()
            .withExternal()
            .source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.COMPLETE)
            .and()
            .withExternal()
            .source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
            .and()
            .withExternal()
            .source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL); // 允许在支付后取消
}

@Bean
public Action<OrderState, OrderEvent> paymentSuccessAction() {
    return context -> {
        // TODO: 发送支付成功的通知
        System.out.println("Payment successful! Sending notification...");
    };
}

“`

  • paymentSuccessAction() 方法创建了一个 Action 实例。
  • System.out.println("Payment successful! Sending notification...") 模拟发送支付成功的通知。

4.4 创建状态机服务类

创建一个服务类来使用状态机:

“`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.stereotype.Service;

@Service
public class OrderStateMachineService {

@Autowired
private StateMachine<OrderState, OrderEvent> stateMachine;

public void pay(Long orderId, Double orderAmount) {
    //将订单ID和订单金额放入状态机的上下文中
    stateMachine.getExtendedState().getVariables().put("orderId", orderId);
    stateMachine.getExtendedState().getVariables().put("orderAmount", orderAmount);

    Message<OrderEvent> message = MessageBuilder.withPayload(OrderEvent.PAY).build();
    stateMachine.sendEvent(message);
}

public void ship() {
    Message<OrderEvent> message = MessageBuilder.withPayload(OrderEvent.SHIP).build();
    stateMachine.sendEvent(message);
}

public void complete() {
    Message<OrderEvent> message = MessageBuilder.withPayload(OrderEvent.COMPLETE).build();
    stateMachine.sendEvent(message);
}

public void cancel() {
    Message<OrderEvent> message = MessageBuilder.withPayload(OrderEvent.CANCEL).build();
    stateMachine.sendEvent(message);
}

public OrderState getCurrentState() {
    return stateMachine.getState().getId();
}

}
“`

  • @Autowired 注入状态机实例。
  • pay(Long orderId, Double orderAmount) 方法用于支付订单。
    • stateMachine.getExtendedState().getVariables().put("orderId", orderId)stateMachine.getExtendedState().getVariables().put("orderAmount", orderAmount) 将订单 ID 和订单金额放入状态机的上下文中,以便 Guard 可以访问。
    • MessageBuilder.withPayload(OrderEvent.PAY).build() 创建一个包含 PAY 事件的消息。
    • stateMachine.sendEvent(message) 发送消息,触发状态转换。
  • ship(), complete(), cancel() 方法类似,用于发送不同的事件。
  • getCurrentState() 方法获取当前状态机的状态。

4.5 创建一个 Controller (可选)

创建一个Controller 来测试服务:

“`java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

@Autowired
private OrderStateMachineService orderStateMachineService;

@GetMapping("/status")
public String getStatus() {
    return orderStateMachineService.getCurrentState().toString();
}

@PostMapping("/pay")
public String pay(@RequestParam Long orderId, @RequestParam Double orderAmount) {
    orderStateMachineService.pay(orderId, orderAmount);
    return "Payment initiated. Current status: " + orderStateMachineService.getCurrentState().toString();
}

@PostMapping("/ship")
public String ship() {
    orderStateMachineService.ship();
    return "Shipping initiated. Current status: " + orderStateMachineService.getCurrentState().toString();
}

@PostMapping("/complete")
public String complete() {
    orderStateMachineService.complete();
    return "Order completed. Current status: " + orderStateMachineService.getCurrentState().toString();
}

@PostMapping("/cancel")
public String cancel() {
    orderStateMachineService.cancel();
    return "Order cancelled. Current status: " + orderStateMachineService.getCurrentState().toString();
}

}
“`

5. 测试应用程序

运行 Spring Boot 应用程序,然后可以使用 Postman 或其他 HTTP 客户端测试 API:

  1. 获取订单状态: GET /status 应该返回 CREATED

  2. 支付订单: POST /pay?orderId=123&orderAmount=100 应该返回 Payment initiated. Current status: PAID。 确保 orderAmount 大于 0,才能成功支付。 如果设置为 0 或者负数,支付将不会成功,状态仍然保持 CREATED.

  3. 再次获取订单状态: GET /status 应该返回 PAID

  4. 发货: POST /ship 应该返回 Shipping initiated. Current status: SHIPPED

  5. 再次获取订单状态: GET /status 应该返回 SHIPPED

  6. 完成订单: POST /complete 应该返回 Order completed. Current status: COMPLETED

  7. 再次获取订单状态: GET /status 应该返回 COMPLETED

  8. 取消订单(在创建状态下): 如果先 GET /status 得到 CREATED , 然后 POST /cancel 应该返回 Order cancelled. Current status: CANCELLED

6. Spring State Machine 的高级特性

除了基本的状态、事件和转换,Spring State Machine 还提供了许多高级特性,包括:

  • 子状态机 (Sub State Machine): 可以将多个状态和转换组合成一个子状态机,用于处理更复杂的状态转换逻辑。
  • 层次状态 (Hierarchical State): 状态可以嵌套,形成层次结构,简化状态机的定义。
  • 并行状态 (Parallel State): 状态机可以同时处于多个状态,用于处理并发逻辑。
  • 事件监听器 (Event Listener): 可以监听状态机的事件,例如状态转换事件、状态进入事件等。
  • 持久化 (Persistence): 可以将状态机的配置和状态持久化到数据库、文件等,以便在应用程序重启后恢复状态。
  • 分布式状态机 (Distributed State Machine): 可以使用 ZooKeeper 等分布式协调服务来构建分布式状态机,实现高可用性和可扩展性。

7. 总结

Spring State Machine 是一个强大的框架,可以帮助你简化状态驱动应用程序的开发。通过使用 Spring State Machine,你可以清晰地定义应用程序的状态和状态之间的转换,使代码更具可读性、可维护性和可测试性。本教程介绍了 Spring State Machine 的基本概念、优势和使用方法,并通过一个简单的订单状态机示例,帮助你快速入门 Spring State Machine。希望本教程能够帮助你构建更健壮、更易于维护的状态驱动应用程序。在实际应用中,可以根据业务需求灵活运用 Spring State Machine 的高级特性,构建更复杂的状态机。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部