玩转spring

稀土掘金

    首页BOT沸点课程直播活动AI刷题NEW

APP

vip
会员

    ppcsdn的头像

玩转Spring状态机 | 京东云技术团队
2023-12-20
3,411
阅读8分钟

说起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{

    @Override
    void handler() {
    System.out.println(“红灯停”);
    }
    }

具体状态类B

/**

  • @description: 具体状态B
    */
    public class GreenLightState extends MyState{

    @Override
    void 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)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。

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

当前状态:也就是状态流转的起始状态。触发事件:引起状态之间流转的一些列动作。响应函数:触发事件到下一个状态之间的规则。目标状态:状态流转的目标状态。

对于组件化的状态机,当前使用较多的主要是两种:一种是Spring 状态机,一种是COLA状态机,这两种状态机的对比如下表所示:
Spring 状态机 COLA 状态机
API 调用 使用 Reactive 的 Mono、Flux 方式进行 API 调用 同步的 API 调用,如果有需要也可以将方法通过 消息队列、定时任务、多线程等方式进行异步调用
代码量 core 包 284 个接口和类 36 个接口和类
生态 非常丰富 较为贫瘠
定制化难度 困难 简单

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

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

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

官方文档: docs.spring.io/spring-stat…

源代码: github.com/spring-proj…
3. Spring状态机实现订单状态流转

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

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

org.springframework.statemachine spring-statemachine-core ${springboot.version}

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

/**

  • @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> {

    /**

    • 配置状态
      */
      @Override
      public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception {
      states.withStates()
      .initial(OrderStatusEnum.WAIT_PAYMENT)
      .end(OrderStatusEnum.FINISH)
      .states(EnumSet.allOf(OrderStatusEnum.class));
      }

    /**

    • 配置状态转换事件关系
      */
      @Override
      public 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 {

    @Resource
    private StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;

    private long id = 1L;

    private Map<Long, Order> orders = Maps.newConcurrentMap();

    @Override
    public 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;
    }

    @Override
    public 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);
    }

    @Override
    public 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);
    }

    @Override
    public 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);
    }

    @Override
    public Map<Long, Order> getOrders() {
    return orders;
    }

    /**

    • 发送状态转换事件
    • @param message
    • @return
      */
      private synchronized boolean sendEvent(Message 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 {

@Resource
private 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状态机的使用方式。文中如有不当之处,欢迎在评论区批评指正。

1. 状态模式
2. Spring状态机
3. Spring状态机实现订单状态流转3.1 环境准备3.2 构造订单状态机3.3 编写状态机监听器3.4 编写订单服务类3.5 测试入口
4. 思考与总结
5. 参考内容

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

[ComfyUI]Flux:繁荣生态魔盒已开启,6款LORA已来,更有MJ6写实动漫风景艺术迪士尼全套

今天&#xff0c;我们将向您介绍一款非常实用的工具——[ComfyUI]Flux。这是一款基于Stable Diffusion的AI绘画工具&#xff0c;旨在为您提供一键式生成图像的便捷体验。无论您是AI绘画的新手还是专业人士&#xff0c;这个工具都能为您带来极大的便利。 在这个教程中&#xff…

阿里云CDN稳定吗?

在互联网服务中&#xff0c;CDN&#xff08;内容分发网络&#xff09;扮演着至关重要的角色&#xff0c;它能够加速网站加载速度&#xff0c;提升用户体验。那么&#xff0c;作为市场上的领先者之一&#xff0c;阿里云的CDN到底稳定吗&#xff1f;九河云来和你说一说吧。 一、…

Matlab实现鹈鹕优化算法(POA)求解路径规划问题

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 鹈鹕优化算法&#xff08;POA&#xff09;是一种受自然界鹈鹕捕食行为启发的优化算法。该算法通过模拟鹈鹕群体在寻找食物时的协作行为&#xff0c;如群飞、潜水和捕鱼等&#xff0c;来探索问题的最优解。POA因其…

C++builder中的人工智能(22):在C+++中读取WAV格式的音频文件

在这篇文章中&#xff0c;我们将探讨如何在C中读取WAV格式的音频文件。音频文件是计算机科学和编程中的一个重要组成部分&#xff0c;正确使用音频可以为娱乐应用程序增添乐趣&#xff0c;或者在业务应用程序中提醒用户重要事件或状态变化。在这篇文章中&#xff0c;我们将解释…

.NET Core 应用程序如何在 Linux 中创建 Systemd 服务 ?

.NET Core 和 Linux 已经成为一个强大的组合&#xff0c;为开发人员提供了一个灵活、高性能的平台来构建和运行应用程序。在 Linux 上部署 .NET Core 应用程序的一个关键方面是利用 systemd 服务来确保应用程序顺利运行&#xff0c;在开机时自动启动&#xff0c;并在失败后重新…

@RestController 源码解读:解决 Web 开发中 REST 服务的疑难杂症

目录 一、RestContrller注解 1.1 查看底层源码 1.2 AliasFor注解说明 1.2.1 注解别名 1.2.2 元数据别名 1.3 value() 方法的作用 一、RestContrller注解 1.1 查看底层源码 首先编写如下内容&#xff1a; RestController public class TestController {} 按住 Ctrl &am…

vs2019托管调试助手 “ContextSwitchDeadlock“错误

错误描述 托管调试助手 "ContextSwitchDeadlock":“CLR 无法从 COM 上下文 0xd183e0 转换为 COM 上下文 0xd18328&#xff0c;这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的情况下处理一个运行时间非常长…

H.264/H.265播放器EasyPlayer.js RTSP播放器关于webcodecs硬解码H265的问题

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、Mp3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方式&#xff0c…

免费在线图片翻译工具:PicTech

文章目录 简介编辑功能 简介 PicTech是一款免费的在线图片翻译工具。图片翻译&#xff0c;顾名思义就是把图片中的文字翻译成另外一种语言&#xff0c;并以图片的形式输出。这种功能在手机的词典软件中似乎还挺常见的&#xff0c;但作为一种在线工具我还是第一次见。 其使用过…

【Vue】Vue3.0(二十)Vue 3.0 中mitt的使用示例

上篇文章 【Vue】Vue3.0&#xff08;十九&#xff09;Vue 3.0 中一种组件间通信方式-自定义事件 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月11日12点23分 文章目录 一、mitt 在…

搭建监控系统Prometheus + Grafana

公司有个技术分享会&#xff0c;但是业务忙&#xff0c;没时间精心准备&#xff0c;所以就匆匆忙忙准备分享一下搭建&#xff08;捂脸哭&#xff09;。技术含量确实不多&#xff0c;但是分享的知识确实没问题。 以下是搭建过程&#xff1a; 一、讲解 Prometheus Prometheus 最…

蓝桥杯真题——班级活动

目录 题目链接&#xff1a;1.班级活动 - 蓝桥云课 题目描述 输入格式 输出格式 样例输入 样例输出 样例说明 评测用例规模与约定 解法一&#xff1a;Map集合处理 举个例子 Java写法&#xff1a; C写法&#xff1a; 运行时间 时间复杂度和空间复杂度 时间复杂度…

Win10下使用Anaconda安装GPU版本PyTorch

一、判断是否有Nvidia(英伟达)显卡 右键开始菜单&#xff0c;在弹出选项中选择任务管理器。 点性能选项&#xff0c;然后点GPU。在右上方会显示GPU名称&#xff0c;只有带NVIDIA的英伟达显卡的电脑才能安装GPU版本&#xff0c;否则其他的就只能安装CPU版本。 二、安装CUDA 首…

精品案例PPT | 企业架构及典型设计方案

本文全面介绍企业架构的理论和实践&#xff0c;包括企业架构的概述、元模型、视图、业务架构、应用架构、数据架构、技术架构以及企业架构管控等内容&#xff0c;有助于企业管理者理解和设计企业级的IT架构&#xff0c;确保架构的全局性、整体性、关联性、可控制性、可实现性和…

java--泛型

欢迎来到我的博客~~欢迎大家对我的博客进行指导~点击进入我的博客主页 目录 一、什么是泛型二、包装类2.1基本数据类型和对应的包装类2.2装箱和拆箱2.3 自动装箱和自动拆箱 三、引出泛型四、泛型类的使用4.1 语法4.2示例 五、泛型如何编译的六、泛型的上界6.1语法6.2 示例 七、…

【CentOS】中的Firewalld:全面介绍与实战应用(下)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、iptables 时代 2、firewalld 时代 二、服务管…

【新人系列】Python 入门(九):数据结构 - 中

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

如何用【钉钉文档】发公告

功能亮点 ✔️借助钉钉文档强大的编辑能力&#xff0c;可以让编写出的公告更加精美。 ✔️将钉钉文档一键导入公告&#xff0c;可以完整保留已经编辑好的格式&#xff0c;无需再手动调整。 ✔️使用钉钉文档&#xff0c;可以将所有公告内容有序沉淀和保存。 &#x1f4a1; 使…

工位管理自动化:Spring Boot企业级工具

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理企业级工位管理系统的相关信息成为必然。开…

相亲小程序(源码+文档+部署+讲解)

最近我在挖掘一些优秀的开源项目时&#xff0c;无意间发现了一个相当给力的系统——相亲小程序管理系统。这个系统不仅功能实用&#xff0c;而且代码结构清晰&#xff0c;易于二次开发。作为一名技术爱好者&#xff0c;我觉得有必要把这个好东西推荐给我的读者们。接下来&#…