玩转 Spring 状态机:更优雅的实现订单状态流转

说起 Spring 状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring 状态机就是状态模式的一种实现,在介绍 Spring 状态机之前,让我们来看看设计模式中的状态模式。

1. 状态模式

状态模式的定义如下:

状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生变化时改变其行为。在状态模式中,一个对象的行为取决于其当前状态,而且可以随时改变这个状态。状态模式将对象的状态封装在不同的状态类中,从而使代码更加清晰和易于维护。当一个对象的状态改变时,状态模式会自动更新该对象的行为,而不需要在代码中手动进行判断和处理。

通常业务系统中会存在一些拥有状态的对象,而且这些状态之间可以进行转换,并且在不同的状态下会表现出不同的行为或者不同的功能,比如交通灯控制系统中会存在红灯、绿灯和黄灯,再比如订单系统中的订单会存在已下单、待支付、待发货、待收货等状态,这些状态会通过不同的行为进行相互转换,这时候在系统设计时就可以使用状态模式。

下面是状态模式的类图:

图片

可以看到状态模式主要包含三种类型的角色:

1、上下文 (Context) 角色: 封装了状态的实例,负责维护状态实例,并将请求委托给当前的状态对象。

2、抽象状态 (State) 角色: 定义了表示不同状态的接口,并封装了该状态下的行为。所有具体状态都实现这个接口。

3、具体状态 (Concrete State) 角色: 具体实现了抽象状态角色的接口,并封装了该状态下的行为。

下面是使用状态模式实现红绿灯状态变更的一个简单案例:

抽象状态类:

/*** @description: 抽象状态类*/
public abstract class MyState {abstract void handler();
}

具体状态类 A

/*** @description: 具体状态A*/
public class RedLightState extends MyState{@Overridevoid handler() {System.out.println("红灯停");}
}

具体状态类 B

/*** @description: 具体状态B*/
public class GreenLightState extends MyState{@Overridevoid handler() {System.out.println("绿灯行");}
}

环境类:维护当前状态对象,并提供了切换状态的方法。

/*** @description: 环境类*/
public class MyContext {private MyState state;public void setState(MyState state) {this.state = state;}public void handler() {state.handler();}
}

测试类

/*** @description: 测试状态模式*/
public class TestStateModel {public static void main(String[] args) {MyContext myContext = new MyContext();RedLightState redLightState = new RedLightState();GreenLightState greenLightState = new GreenLightState();myContext.setState(redLightState);myContext.handler(); //红灯停myContext.setState(greenLightState);myContext.handler(); //绿灯行}
}

下面是对应的执行结果

图片

可以发现,使用状态模式中的状态类在一定程度上也消除了 if-else 逻辑校验,看到这里, 有些人可能会有疑问:状态模式和策略模式的区别是什么呢?

状态模式更关注对象在不同状态的行为和状态之间的流转,而策略模式更关注对象不同策略的选择。

上面我们介绍了设计模式中的状态模式,接下来我们来看看 Spring 状态机。

2. Spring 状态机

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型 。说白了,就是指一张状态转换图。 状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。Spring 也提供了一个很好的解决方案。Spring 中的组件名称就叫作状态机(StateMachine)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。

通过定义,我们很容易分析得到状态机应当具备一下几个要素:

1. 当前状态: 也就是状态流转的起始状态。

2. 触发事件: 引起状态之间流转的一些列动作。

3. 响应函数: 触发事件到下一个状态之间的规则。

4. 目标状态: 状态流转的目标状态。

对于组件化的状态机,当前使用较多的主要是两种:一种是 Spring 状态机,一种是 COLA 状态机,这两种状态机的对比如下表所示:

图片

可以看到,Spring 状态机锁提供的内容较为丰富,当然对于自定义的支持就不如 COLA 状态机好,如果对自定义的需求比较高,那建议使用 COLA 状态机。

本文以 Spring 状态机为例,展示如何在业务系统中使用状态机。

为了便于大家了解 Spring 状态机的实现原理和使用方式以及其提供的功能,下面列出了官方文档和源码,感兴趣的同学可以阅读阅读。

官方文档:

  • https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-states

源代码:

  • https://github.com/spring-projects/spring-statemachine

3. Spring 状态机实现订单状态流转

对于状态模式,Spring 封装好了一个组件,就叫状态机(StateMachine)。Spring 状态机可以帮助我们开发者简化状态控制的开发过程,让状态机结构更加层次化。下面用 Spring 状态机模拟一个订单状态流转的过程。

3.1 环境准备

首先,如果要使用 spring 状态机,需要引入对应的 jar 包,这里我的 springboot 版本是:2.2.1.RELEASE

<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-core</artifactId><version>${springboot.version}</version>
</dependency>

下面是简化的订单的定义,以及订单状态和订单转换行为的枚举

/*** @description: 模拟订单类*/
@Data
public class Order {private Long orderId;private OrderStatusEnum orderStatus;
}/*** @description: 订单状态*/
public enum OrderStatusEnum {// 待支付WAIT_PAYMENT,// 待发货WAIT_DELIVER,// 待收货WAIT_RECEIVE,// 完成FINISH;
}/*** @description:订单状态转换行为*/
public enum OrderStatusChangeEventEnum {//支付PAYED,//发货DELIVERY,//收货RECEIVED;
}
3.2 构造订单状态机

在引入 jar 包之后,需要构建一个针对订单状态流转的状态机

订单状态机配置类如下:

/*** @description: 订单状态机*/
@Configuration
@EnableStateMachine
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum> {/*** 配置状态*/@Overridepublic void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception {states.withStates().initial(OrderStatusEnum.WAIT_PAYMENT).end(OrderStatusEnum.FINISH).states(EnumSet.allOf(OrderStatusEnum.class));}/*** 配置状态转换事件关系*/@Overridepublic void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> transitions) throws Exception {transitions.withExternal().source(OrderStatusEnum.WAIT_PAYMENT).target(OrderStatusEnum.WAIT_DELIVER).event(OrderStatusChangeEventEnum.PAYED).and().withExternal().source(OrderStatusEnum.WAIT_DELIVER).target(OrderStatusEnum.WAIT_RECEIVE).event(OrderStatusChangeEventEnum.DELIVERY).and().withExternal().source(OrderStatusEnum.WAIT_RECEIVE).target(OrderStatusEnum.FINISH).event(OrderStatusChangeEventEnum.RECEIVED);}
}
3.3 编写状态机监听器

监听状态变更事件,完成状态转换。

/*** @description: 状态监听*/
@Component
@WithStateMachine
@Transactional
public class OrderStatusListener {@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")public boolean payTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);System.out.println("支付,状态机反馈信息:" + message.getHeaders().toString());return true;}@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")public boolean deliverTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);System.out.println("发货,状态机反馈信息:" + message.getHeaders().toString());return true;}@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")public boolean receiveTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.FINISH);System.out.println("收货,状态机反馈信息:" + message.getHeaders().toString());return true;}}
3.4 编写订单服务类

模拟对订单的一些业务操作

/*** @description: 订单服务*/
@Service
public class OrderServiceImpl implements OrderService {@Resourceprivate StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;private long id = 1L;private Map<Long, Order> orders = Maps.newConcurrentMap();@Overridepublic Order create() {Order order = new Order();order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);order.setOrderId(id++);orders.put(order.getOrderId(), order);System.out.println("订单创建成功:" + order.toString());return order;}@Overridepublic Order pay(long id) {Order order = orders.get(id);System.out.println("尝试支付,订单号:" + id);Message message = MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED).setHeader("order", order).build();if (!sendEvent(message)) {System.out.println(" 支付失败, 状态异常,订单号:" + id);}return orders.get(id);}@Overridepublic Order deliver(long id) {Order order = orders.get(id);System.out.println(" 尝试发货,订单号:" + id);if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY).setHeader("order", order).build())) {System.out.println(" 发货失败,状态异常,订单号:" + id);}return orders.get(id);}@Overridepublic Order receive(long id) {Order order = orders.get(id);System.out.println(" 尝试收货,订单号:" + id);if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED).setHeader("order", order).build())) {System.out.println(" 收货失败,状态异常,订单号:" + id);}return orders.get(id);}@Overridepublic Map<Long, Order> getOrders() {return orders;}/*** 发送状态转换事件* @param message* @return*/private synchronized boolean sendEvent(Message<OrderStatusChangeEventEnum> message) {boolean result = false;try {orderStateMachine.start();result = orderStateMachine.sendEvent(message);} catch (Exception e) {e.printStackTrace();} finally {if (Objects.nonNull(message)) {Order order = (Order) message.getHeaders().get("order");if (Objects.nonNull(order) && Objects.equals(order.getOrderStatus(), OrderStatusEnum.FINISH)) {orderStateMachine.stop();}}}return result;}
}
3.5 测试入口

这里编写一个 controller 模拟 c 端用户请求,为了便于展示,这里使用一个测试方法完成所有的操作

@RestController
public class OrderController {@Resourceprivate OrderService orderService;@RequestMapping("/testOrderStatusChange")public String testOrderStatusChange(){orderService.create();orderService.create();orderService.pay(1L);orderService.deliver(1L);orderService.receive(1L);orderService.pay(2L);orderService.deliver(2L);orderService.receive(2L);System.out.println("全部订单状态:" + orderService.getOrders());return "success";}}

下面是对应的执行结果

图片

可以看到 spring 状态机很好的控制了订单在各个状态之间的流转。

4. 思考与总结

思考:针对状态机的特点,还有其他思路实现一个状态机吗?下面是一些常规思路,如果还有其他方法欢迎在评论区留言。

  1. 消息队列方式

订单状态的流转可以通过 MQ 发布一个事件,消费者根据业务条件把订单状态进行流转,可以根据不同的事件发送到不同的 Topic。

  1. 定时任务驱动

每隔一段时间启动一下 job,根据特定的状态从数据库中拿对应的订单记录,然后判断订单是否有条件到达下一个状态。

  1. 规则引擎方式

业务团队可以在规则引擎里编写一系列的状态及其对应的转换规则,由规则引擎根据已经加载的规则对输入数据进行解析,根据解析的结果执行相应的动作,完成状态流转。

总结:

本文主要介绍了设计模式中的状态模式,并在此基础上介绍了 Spring 状态机相关的概念,并根据常见的订单流转场景,介绍了 Spring 状态机的使用方式。文中如有不当之处,欢迎在评论区批评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/745659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorch之诗词生成3--utils

先上代码&#xff1a; import numpy as np import settingsdef generate_random_poetry(tokenizer, model, s):"""随机生成一首诗:param tokenizer: 分词器:param model: 用于生成古诗的模型:param s: 用于生成古诗的起始字符串&#xff0c;默认为空串:return: …

linux 安装/升级 svn

文章目录 下载最新版本安装包安装 下载最新版本安装包 wget https://dlcdn.apache.org/subversion/subversion-1.14.3.tar.gz tar -zxf subversion-1.14.3.tar.gz cd subversion-1.14.3 安装 ./configure 报错&#xff0c;提示缺少 apr-util 库&#xff0c;有的环境可能 apr 库…

人工智能|机器学习——CURE聚类算法(层次聚类)

1.CURE聚类概述 绝大多数聚类算法或者擅长处理球形和相似大小的聚类&#xff0e;或者在存在孤立点时变得比较脆弱。CURE采用了一种新颖的层次聚类算法&#xff0e;该算法选择基于质心和基于代表对象方法之间的中间策略。它不同于单个质心或对象来代表一个类&#xff0c;而是选择…

大话设计模式——6.工厂方法模式(Factory Method Pattern)

1.介绍 工厂方法模式也称工厂模式&#xff0c;是简单工厂模式的进一步抽象。定义一个用于创建对象的接口&#xff0c;使一个类的实例化延迟到其子类&#xff0c;让子类决定实例化哪个类。通过工厂父类定义负责创建产品的公共接口&#xff0c;通过子类确定所需要创建的类型。 属…

《ElementPlus 与 ElementUI 差异集合》el-input 多包裹一层 el-input__wrapper

差异 element-ui el-input 中&#xff0c;<div class"el-input"> 下一级就是 <input> 标签 &#xff1b;element-plus el-input中&#xff0c;<div class"el-input"> 和 <input> 标签之间多了一层 <div class"el-input__…

Nginx、LVS、HAProxy工作原理和负载均衡架构

当前大多数的互联网系统都使用了服务器集群技术&#xff0c;集群是将相同服务部署在多台服务器上构成一个集群整体对外提供服务&#xff0c;这些集群可以是 Web 应用服务器集群&#xff0c;也可以是数据库服务器集群&#xff0c;还可以是分布式缓存服务器集群等等。 在实际应用…

提升零售行业竞争力的信息抽取技术应用与实践

一、引言 在当今快速发展的零售行业中&#xff0c;沃尔玛、家乐福等大型连锁超市为消费者提供了丰富的日常食品和日用品。为了进一步提升客户体验和优化库存管理&#xff0c;这些零售巨头纷纷开始探索和应用先进的信息抽取技术。 本文将深入探讨一个成功的信息抽取项目&#…

使用HttpRequest工具类调用第三方URL传入普通以及文件参数并转换MultipartFile成File

使用HttpRequest工具类调用第三方URL传入普通以及文件参数 一、依赖及配置二、代码1、模拟第三方服务2、调用服务3、效果实现 一、依赖及配置 <!--工具依赖--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId&g…

嵌入式单片机学习思路感想分享

今天看到了一个提问,原话如下: 曾经干了8年单片机工程师,对工程师从入门,到入行,再到普通,再到高级,整个路径还算清晰,比如什么阶段,会碰到什么瓶颈,怎么突破,我都经历过。 这个同学,有个典型的问题,就是学得太多且杂了,估计稍微复杂点的项目,做不出来。 现在…

Spring Boot 中@Scheduled是单线程还是多线程?

在开发Spring Boot应用程序时&#xff0c;定时任务是一项常见的需求。Spring Boot提供了Scheduled注解&#xff0c;可用于将方法标记为定时任务&#xff0c;并在预定的时间间隔内执行。那么Scheduled注解的执行方式是单线程执行&#xff0c;还是多线程执行&#xff1f;Schedule…

docker小白第十二天

docker小白第十二天 docker network简介 docker不启动时默认的网络情况。 # 停止docker服务 systemctl stop docker.socket systemctl stop docker # 查看docker镜像 docker images输入查看docker镜像命令后&#xff0c;显示未连接到docker服务器 docker启动时网络情况 sy…

使用BBDown下载bilibili视频的方法

一款命令行式哔哩哔哩下载器. Bilibili Downloader. 下载地址 https://github.com/nilaoda/BBDown 功能 番剧下载(Web|TV|App) 课程下载(Web) 普通内容下载(Web|TV|App) 合集/列表/收藏夹/个人空间解析 多分P自动下载 选择指定分P进行下载 选择指定清晰度进行下载 下载外挂字幕…

html--bug

文章目录 html html <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>老师</title><style>body {background-color: #008000;margin: 0px;cursor: none;overflow: hidden;}</style></head><bod…

88. 合并两个有序数组 (Swift版本)

题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并…

深度解析Java JDK 1.8中Stream流的源码实现:带你探寻数据流的奥秘

文章目录 一、 Stream流概述1.1 什么是Stream流&#xff0c;以及它的主要特点和优势1.2 Stream流的基本操作&#xff1a;过滤、映射、排序等 二、 Stream流源码解析2.1 接口和基本概念2.2 创建流2.3 源码分析2.3.1 流的起始2.3.2 流的初始2.3.3 认识BaseStream2.3.4 Stream接口…

java-ssm-jsp基于java的餐厅点餐系统的设计与实现

java-ssm-jsp基于java的餐厅点餐系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

RabbitMQ:1.概述及安装

概述 AMQP协议 MQ Message Queue&#xff08;消息队列&#xff09;是在消息的传输过程中保存消息的容器&#xff0c;多用于系统之间的异步通信 AMQP Advanced Message Queuing Protocol(高级消息队列协议)是一个网络协议&#xff0c;2006年AMQP规范发布【类比HTTP】 专门为消…

基于单片机的公交车IC卡操作系统的设计

目 录 摘 要 III Abstract IV 前 言 1 第一章 绪论 2 1.1 设计的背景和意义 2 1.2 设计的现状和发展 2 1.3 设计的目的与意义 2 第二章 总体设计 4 2.1 总体方案的设计与实现 4 2.1.1 主要设计的内容 4 2.1.2 系统的总体设计 4 2.2 系统方案论证 5 2.2.1 单片机的选择 6 2.2.2…

Ubuntu虚拟机的IP总频繁变化,导致Xshell断开连接

文章目录 一、IP变化的原因二、解决方法&#xff1a;固定IP三、参考文章 一、IP变化的原因 1.DHCP协议 虚拟机系统(Ubuntu、CentOS、UOS等Linux系统)启动后&#xff0c;加入本地局域网网络时&#xff0c;会向本地网络申请租约一个IP地址&#xff0c;租约时长不定。我这里租约时…

独立开发的轻量级简洁开源论坛BBS PHP源码 – 2023新版发布

最新的轻量级开源论坛php源码发布啦&#xff01;这是一款独立开发的论坛系统&#xff0c;可以帮助你快速地开发出你想要的网站。 如果你是PHP初学者&#xff0c;这款论坛系统非常适合你入门学习。不过&#xff0c;需要注意的是&#xff0c;由于它并没有进行商业化改造&#xf…