玩转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状态机的实现原理和使用方式以及其提供的功能,下面列出了官方文档和源码,感兴趣的同学可以阅读阅读。

官方文档: Spring Statemachine - Reference Documentation

源代码: 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。

2. 定时任务驱动

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

3. 规则引擎方式

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

总结:

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

5. 参考内容

Spring Statemachine - Reference Documentation

设计模式-状态模式-腾讯云开发者社区-腾讯云

「聊设计模式」之状态模式(State)-腾讯云开发者社区-腾讯云

以电商下单场景为例,玩一玩有限状态机的流转 - 掘金

状态机的介绍和使用 | 京东物流技术团队 - 京东云开发者的个人空间 - OSCHINA - 中文开源技术交流社区

状态模式之订单状态流转(Spring状态机) - 掘金

文章转载自:京东云开发者

原文链接:https://www.cnblogs.com/Jcloud/p/17915667.html

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

[Encryptedd@mailfence.com].faust 勒索病毒肆虐:如何恢复被加密的数据文件?

导言&#xff1a; 在网络安全的战场上&#xff0c;[backupsairmail.cc].faust [Deciphermailfence.com].faust[Encrypteddmailfence.com].faust[support2022cock.li].faust [tsai.shenmailfence.com].faust勒索病毒是一种极具破坏性的恶意软件。本文91数据恢复将深入介绍该病毒…

【krita】实时绘画 入门到精通 海报+电商+装修+人物

安装插件 首先打开comfyUI&#xff0c;再打开krita&#xff0c;出现问题提示&#xff0c; 打开 cd custom_nodes 输入命令 安装控件 git clone https://github.com/Acly/comfyui-tooling-nodes.git krita基础设置 设置模型 设置lora &#xff08;可设置lora强度 增加更多…

◢Django md5加密与中间件middleware

utils文件夹是重新建立的&#xff08;与migrations同级&#xff09;&#xff0c;该文件夹下主要存放工具&#xff0c;就像static文件夹下只存放静态文件一样 加密 在utils文件夹下建立encrypt.py文件 from django.conf import settings import hashlib def md5(data_string)…

Airtest1.2.7新增断言API介绍

1. 前言 1.2.7版本的Airtest中&#xff0c;一个很重要的功能是 新增了非常丰富的断言API &#xff0c;今天我们就来详细看一下Airtest都给我们提供了哪些断言语句。 2. 旧版Airtest提供的断言语句 先回顾下&#xff0c;旧版Airtest一直以来&#xff0c;都只给我们提供了2种断言…

Samtec信号完整性 这家连接器的设计很优秀!

【Samtec技术研发&#xff1a;信号完整性设计】 1. 什么是信号完整性&#xff1f; 信号完整性需要在整个系统和组件设计过程中加以考虑。与过去不同的是&#xff0c;互连不再是事后考虑的问题。随着上升时间的缩短和时钟频率的提高&#xff0c;曾经被认为是电气透明的连接器和…

阿里云经济型、通用算力型、计算型、通用型、内存型云服务器最新活动报价

阿里云作为国内领先的云计算服务提供商&#xff0c;提供了多种规格的云服务器供用户选择。为了满足不同用户的需求&#xff0c;阿里云推出了经济型、通用算力型、计算型、通用型和内存型等不同类型的云服务器。下面将详细介绍这些云服务器的最新活动报价。 一、阿里云特惠云服…

机器学习数据的清洗,转化,汇总及建模完整步骤(基于Titanic数据集)

目录 介绍&#xff1a; 一、数据 二、检查数据缺失 三、数据分析 四、数据清洗 五、数据类别转化 六、数据汇总和整理 七、建模 介绍&#xff1a; 线性回归是一种常用的机器学习方法&#xff0c;用于建立一个输入变量与输出变量之间线性关系的预测模型。线性回归的目标…

工业4.0|工业物联平台有多重要?IoTopo 深度解析

随着时代的发展&#xff0c;科学技术日新月异&#xff0c;人们进入了信息化的时代。现今&#xff0c;在物联网的逐渐普及下&#xff0c;物联网技术应用于工业中&#xff0c;也显得尤为重要&#xff1b;在工业网络和移动计算持续影响着制造业和工业环境的大环境中&#xff0c;这…

计算机毕业设计 基于SpringBoot的大学生平时成绩量化管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Python---TCP服务端程序开发

1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象绑定端口号设置监听等待接受客户端的连接请求接收数据发送数据关闭套接字 2. socket 类的介绍 导入 socket 模块import socket 创建服务端 socket 对象socket.socket(AddressFamily, Type) 参数说明: AddressF…

SpringMVC:执行原理详解、配置文件和注解开发实现 SpringMVC

文章目录 SpringMVC - 01一、概述二、SpringMVC 执行原理三、使用配置文件实现 SpringMVC四、使用注解开发实现 SpringMVC1. 步骤2. 实现 五、总结注意&#xff1a; SpringMVC - 01 一、概述 SpringMVC 官方文档&#xff1a;点此进入 有关 MVC 架构模式的内容见之前的笔记&a…

【AI数学】NeRF中的球面谐波函数(Spherical Harmonics)

球面谐波&#xff08;SH&#xff09;因为其良好的性质活跃在NeRF、Plenoxels、3DGS等显隐式场景表示的方法中。 问&#xff1a;球面谐波是什么&#xff1f; 答&#xff1a;一组基函数。可以理解为傅里叶分解的一种特殊形式&#xff0c;即“任何函数都可以用这组基的算术组合来近…

记录 | gdb调试的基本命令

r (run) 运行程序 b (breakpoint) 打断点&#xff0c;比如 b func(打到函数) b 5(打到第5行)(当前文件) b main.cpp:5(main.cpp的第5行) b MyClass::func() (打到类的成员函数func()、在类内可以 p this 打印、p *this、p this->name) tb (temporary breakpoint) 临时断…

ctfshow(web190-web200)

目录 web190 web191 web192 web193 web194 web195 web196 web197 web198 web199 web200 web190 为什么要有admin呢 这也是试出来的 这个admin必须是数据库中存在的 这样才能使用布尔注入 因为这个时候登录 有两种返回结果 一种密码错误 一种就是用户名错误 admin an…

前端性能监控和错误监控

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

Ubuntu 常用命令之 passwd 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 在Ubuntu系统中&#xff0c;passwd命令用于更改用户的密码。系统管理员可以使用此命令更改任何用户的密码&#xff0c;而普通用户只能更改自己的密码。 passwd命令的参数如下 -l, --lock&#xff1a;锁定密码&#xff0c;使账户…

拥抱鸿蒙 - 在展讯T606平台上的探索与实践

前 言 自OpenHarmony 问世后受到了社会各界的广泛关注&#xff0c;OpenHarmony 的生态系统在如火如荼的发展。 酷派作为一家积极拥抱变化的公司&#xff0c;经过一段时间的探索与实践&#xff0c;成功实现将OpenHarmony 系统接入到展讯平台上&#xff0c;我们相信这是一个重要…

Android Studio 安装和使用

前些天&#xff0c;打开了几年前的一个Android Studio app项目&#xff0c;使用安卓虚拟机仿真app崩溃&#xff0c;怀疑是不是中间升级过Android Studio导致异常的&#xff0c;马上脑子一热卸载了&#xff0c;结果上次踩过的坑&#xff0c;一个没少又踩一次&#xff0c;谨以此文…

大数据技术基本功-数据采集

产品指南&#xff5c;DataScale自定义采集器功能介绍产品指南&#xff5c;开发 DataScale Collector​​​​​​​

python用什么软件编写程序,python编写的软件有哪些

大家好&#xff0c;本文将围绕python用什么软件编写程序展开说明&#xff0c;python编写的软件有哪些是一个很多人都想弄明白的事情&#xff0c;想搞清楚用什么软件编写python需要先了解以下几个事情。 大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python可以用什么…