专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
中国消费者报  ·  3·15在行动 | ... ·  昨天  
中国消费者报  ·  3·15在行动 | ... ·  昨天  
河南省消费者协会  ·  当心资金被盗!中国银联严正声明! ·  2 天前  
河南省消费者协会  ·  当心资金被盗!中国银联严正声明! ·  2 天前  
51好读  ›  专栏  ›  Java基基

玩转 Spring 状态机:打造灵活高效的业务逻辑流,太优雅了!

Java基基  · 公众号  · 科技自媒体  · 2025-01-10 11:55

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本

来源:blog.csdn.net/wenbin729392753


在现代电子商务系统中,订单的状态管理是一个非常重要的环节。订单从创建到最终完成或取消,通常会经历多个状态的转换。如何高效地管理这些状态流转,并在系统中灵活地扩展状态和行为,是我们在开发中需要解决的问题。

本文将详细介绍如何在Spring Boot项目中使用 Spring Statemachine 框架来实现订单状态流转控制。

一、Spring Statemachine概述

Spring Statemachine 是由Spring团队提供的一个轻量级状态机框架。它为开发者提供了一种简便且强大的方式来管理复杂的状态流转逻辑,尤其适用于订单处理、工作流引擎等需要状态管理的场景。

Spring Statemachine 具有以下特点:

  • 灵活的状态配置: 通过Java配置或外部配置文件定义状态和状态转换。
  • 支持并发状态和嵌套状态: 可以管理复杂的状态图。
  • 与Spring生态系统的良好集成: 易于与Spring Boot、Spring Security等集成。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、订单状态流转场景分析

在一个典型的订单处理流程中,订单可能会经历以下几个状态:

  • 新建 (NEW): 订单刚刚创建,等待支付。
  • 已支付 (PAID): 用户完成支付,等待发货。
  • 已发货 (SHIPPED): 订单已经发货,等待收货。
  • 已完成 (COMPLETED): 用户确认收货,订单完成。
  • 已取消 (CANCELLED): 订单在任意状态下都可能被取消。

这些状态之间可能存在以下转换关系:

  • 新建 -> 已支付
  • 已支付 -> 已发货
  • 已发货 -> 已完成
  • 新建 -> 已取消
  • 已支付 -> 已取消

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

三、Spring Statemachine的原理与实现

Spring Statemachine 中,状态和状态转换(Transitions)通过配置来定义。每个状态转换都由事件(Events)触发,从而使状态从一个状态流转到另一个状态。

1. 引入依赖

首先,在Spring Boot项目中引入 Spring Statemachine 的依赖。

<dependency>
 <groupId>org.springframework.statemachinegroupId>
 <artifactId >spring-statemachine-starterartifactId>
dependency>

2. 定义状态和事件

接下来,我们定义订单状态和事件。

public enum OrderStates {
    NEW, PAID, SHIPPED, COMPLETED, CANCELLED
}
 
public enum OrderEvents {
    PAY, SHIP, COMPLETE, CANCEL
}

3. 配置状态机

使用 StateMachineConfigurerAdapter 来配置状态机,包括状态、事件、监听器和持久化及其转换关系。

@Configuration
@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter<OrderStatesOrderEvents{
 
    private static final Logger log = LoggerFactory.getLogger(StateMachineConfig.class);
 
    @Override
    public void configure(StateMachineConfigurationConfigurer config)
            throws Exception 
{
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }
 
    @Override
    public void configure(StateMachineStateConfigurer states)
            throws Exception 
{
        states
                .withStates()
                .initial(OrderStates.NEW)
                .end(OrderStates.COMPLETED)
                .end(OrderStates.CANCELLED)
                .states(EnumSet.allOf(OrderStates.class));
    }
 
    @Override
    public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
        transitions
                .withExternal()
                .source(OrderStates.NEW).target(OrderStates.PAID).event(OrderEvents.PAY)
                .and()
                .withExternal()
                .source(OrderStates.PAID).target(OrderStates.SHIPPED).event(OrderEvents.SHIP)
                .and()
                .withExternal()
                .source(OrderStates.SHIPPED).target(OrderStates.COMPLETED).event(OrderEvents.COMPLETE)
                .and()
                .withExternal()
                .source(OrderStates.NEW).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
                .and()
                .withExternal()
                .source(OrderStates.PAID).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL);
    }
 
    @Bean
    public StateMachineListener listener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State from, State to) {
                log.info("State change to: {}", to.getId());
            }
 
            @Override
            public void stateMachineError(StateMachine stateMachine, Exception exception) {
                log.error("Exception caught: {}", exception.getMessage(), exception);
            }
 
            @Override
            public void eventNotAccepted(Message message) {
                Order order = (Order) message.getHeaders().get("order");
                log.error("Order state machine can't change state {} --> {}", Objects.requireNonNull(order).getStatus(), message.getPayload());
            }
        };
    }
 
    @Bean
    public StateMachinePersist inMemoryStateMachinePersist() {
        return new StateMachinePersist<>() {
            private final Map> contexts = new HashMap<>();
 
            @Override
            public void write(StateMachineContext context, String contextObj) {
                contexts.put(contextObj, context);
            }
 
            @Override
            public StateMachineContext read(String contextObj) {
                return contexts.get(contextObj);
            }
        };
    }
}

4. 配置状态改变后的处理器

使用 @WithStateMachine 来配置状态机流转后的后续逻辑,比如更新订单状态、发邮件等。

@Service
@WithStateMachine
public class OrderStateChangeHandler {
 
    private static final Logger log = LoggerFactory.getLogger(OrderStateChangeHandler.class);
 
    @OnTransition(source = "NEW", target = "PAID")
    public void payTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        log.info("Handle Pay Order:{}", order);
        // 其他业务 如保存订单状态
        Objects.requireNonNull(order).setStatus(OrderStates.PAID);
        //orderRepository.save(order);
 
    }
 
    @OnTransition(source = "PAID", target = "SHIPPED")
    public void shipTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        log.info("Handle Ship Order:{}", order);
        // 其他业务 如更新订单
        Objects.requireNonNull(order).setStatus(OrderStates.SHIPPED);
//        orderMapper.updateById(order);
    }
    @OnTransition(source = "SHIPPED", target = "COMPLETED")
    public void completeTransition(Message message) {
        Order order = (Order) message.getHeaders().get("order");
        log.info("Handle Complete Order:{}", order);
 
        // 其他业务 如更新订单
        Objects.requireNonNull(order).setStatus(OrderStates.COMPLETED);
//        orderMapper.updateById(order);
    }
 
}

5. 使用状态机控制订单状态流转

配置好状态机后,我们可以在业务逻辑中使用它来控制订单的状态流转。

@Service
public class OrderService {
 
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
 
    private final StateMachine stateMachine;
 
    public OrderService(StateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }
 
    public void payOrder(Integer id) {
        log.info("Pay Order: {}", id);
        Order order = new Order("12345""Sample Order", OrderStates.NEW, new BigDecimal("99.99"));
        // 模拟支付
//        payService.payOrder(1);
        Message message = MessageBuilder.withPayload(OrderEvents.PAY).setHeader("order", order).build();
        stateMachine.sendEvent(Mono.just(message)).subscribe();
    }
 
    public void shipOrder(Integer id) {
        log.info("Ship Order: {}", id);
        Order order = new Order("12345""Sample Order", OrderStates.PAID, new BigDecimal("99.99"));
        // 模拟发货
//        tradeService.shipOrder(1);
        Message message = MessageBuilder.withPayload(OrderEvents.SHIP).setHeader("order", order).build();
        stateMachine.sendEvent(Mono.just(message)).subscribe();
    }
 
    public void completeOrder(Integer id) {
        log.info("Complete Order: {}", id);
        Order order = new Order("12345""Sample Order", OrderStates.SHIPPED, new BigDecimal("99.99"));
 
        Message message = MessageBuilder.withPayload(OrderEvents.COMPLETE).setHeader("order", order).build();
        stateMachine.sendEvent(Mono.just(message)).subscribe();
    }
 
}

6. 控制订单流转

在控制层中我们可以根据不同的请求来触发状态转换:

@GetMapping("/order/pay/{id}")
public String payOrder(@PathVariable("id") Integer id) {
     orderService.payOrder(id);
     return "Order paid ";
}
 
@GetMapping("/order/ship/{id}")
public String shipOrder(@PathVariable("id") Integer id) {
    orderService.shipOrder(id);
    return "Order shipped ";
}
 
@GetMapping("/order/complete/{id}")
 public String completeOrder(@PathVariable("id") Integer id) {
    orderService.completeOrder(id);
    return "Order completed ";
}

四、原理解析

Spring Statemachine 的核心是有限状态机模型,它使用状态、事件、和转换来控制业务流程。状态机会在不同的状态之间进行转换,每次转换都会触发相关的操作。状态机还支持嵌套状态、并发状态等复杂场景。

  • 状态(State): 表示业务流程中的不同阶段。
  • 事件(Event): 触发状态转换的操作。
  • 转换(Transition): 状态之间的变化,通常由事件驱动。

Spring Statemachine 通过定义状态机的配置,允许开发者灵活地管理状态和转换逻辑。

五、测试

当我们按照上面的步骤,配置好了状态机之后,接下来我们测试一下,配置的状态机是否能达到预期的目的。

1.NEW --> SHIPPED

因为我们配置的订单流转规则中, NEW 只能转换到 PAID CANCELLED ,所以我们期望的是订单不能流转成功。







请到「今天看啥」查看全文


推荐文章
河南省消费者协会  ·  当心资金被盗!中国银联严正声明!
2 天前
河南省消费者协会  ·  当心资金被盗!中国银联严正声明!
2 天前
热辣动态图  ·  讲真,这是我见过最长的腿
7 年前
早安晚安心语  ·  女人身上最漂亮的东西,你还在吗?
7 年前