0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

玩转Spring状态机

京东云 来源:jf_75140285 作者:jf_75140285 2024-06-25 14:21 次阅读

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

1. 状态模式

状态模式的定义如下:

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

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

下面是状态模式类图:

wKgaomZ46Y6AHiVCAACxvB4H4AM171.png



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

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(); //绿灯行
    }
}

下面是对应的执行结果

wKgZomZ46Y6ARtlKAAAp05VjWVM336.png



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

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

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

2. Spring状态机

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

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

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

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

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

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

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


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

可以看到,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 > {

    /**
     * 配置状态
     */
    @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< 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 {

    @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";
    }

}

下面是对应的执行结果

wKgaomZ46Y-AYMukAAIZ6K9opCo758.png



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

4. 思考与总结

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

1. 消息队列方式

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

2. 定时任务驱动

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

3. 规则引擎方式

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

总结:

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

5. 参考内容

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

https://cloud.tencent.com/developer/article/2198477?areaId=106001

https://cloud.tencent.com/developer/article/2360708?areaId=106001

https://juejin.cn/post/7087064901553750030

https://my.oschina.net/u/4090830/blog/10092135

https://juejin.cn/post/7267506576448929811

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7972

    浏览量

    149245
  • 封装
    +关注

    关注

    124

    文章

    7427

    浏览量

    141540
  • 状态机
    +关注

    关注

    2

    文章

    487

    浏览量

    27280
  • spring
    +关注

    关注

    0

    文章

    334

    浏览量

    14208
  • 组件
    +关注

    关注

    1

    文章

    434

    浏览量

    17662
收藏 人收藏

    评论

    相关推荐

    Spring状态机的实现原理和使用方法

    说起 Spring 状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring
    的头像 发表于 12-26 09:39 1344次阅读
    <b class='flag-5'>Spring</b><b class='flag-5'>状态机</b>的实现原理和使用方法

    状态机编程

    状态机编程基于状态机的按键输入软件接口设计一般的教课书中给出的按键输入软件接口程序通常非常简单,在程序中一旦检测到按键输入口为低电平时(图9-2),便采用(调用)软件延时程序延时10ms。然后再
    发表于 07-10 18:00

    如何写好状态机

    一篇经典文献,详细讲解了一段、两段、三段式状态机的实现,效率、优缺点。看完后相信会对状态机有一个详细的了解。 状态机是逻辑设计的重要内容,状态机的设计水平直接反应工程师的逻辑功底,所以
    发表于 10-24 11:43

    状态机

    控制状态机控制状态机的初始化和状态转换的最佳方法是使用枚丽型输入控件。一般使用自定义类型的枚丽变量。使用子定义类型的枚丽变量可以是控件和实例乊间存在关联,使得添加或删除状态时所有的枚丽
    发表于 02-13 12:39

    状态机如何暂停

    程序一运行 就开始自动运行程序 状态机各种各种状态开始执行 我这里是布尔变量 每一秒点亮一个布尔按钮。我现在想在界面增加一个暂停按钮 当点暂停时候 此时暂停按钮文字成为继续 如果 状态机执行第二步
    发表于 04-09 09:23

    状态机是什么?什么是消息触发类型的状态机

    状态机可归纳为哪几个要素?状态机可分为哪几种?什么是消息触发类型的状态机
    发表于 04-19 06:02

    什么是状态机状态机是如何编程的?

    什么是状态机状态机是如何编程的?
    发表于 10-20 07:43

    什么是状态机

    一. 什么是状态机我们以生活中的小区的停车系统为例:停车杆一般没车的是不动的(初态),有车来的时候需要抬杆(状态1),车通过需要放杆(状态2),如果在放杆的过程中突然有车,又需要抬杆(状态
    发表于 01-06 08:01

    什么是状态机

    目录1 前言2 状态机2.1 什么是状态机2.2 状态机的概念2.3 使用状态机写键盘的思路3 代码实例3.1 使用软件3.2 protues电路图3.2
    发表于 01-24 06:23

    状态机原理及用法

    状态机原理及用法状态机原理及用法状态机原理及用法
    发表于 03-15 15:25 0次下载

    状态机概述 如何理解状态机

    本篇文章包括状态机的基本概述以及通过简单的实例理解状态机
    的头像 发表于 01-02 18:03 1w次阅读
    <b class='flag-5'>状态机</b>概述  如何理解<b class='flag-5'>状态机</b>

    FPGA:状态机简述

    本文目录 前言 状态机简介 状态机分类 Mealy 型状态机 Moore 型状态机 状态机描述 一段式
    的头像 发表于 11-05 17:58 6539次阅读
    FPGA:<b class='flag-5'>状态机</b>简述

    什么是状态机状态机5要素

    玩单片机还可以,各个外设也都会驱动,但是如果让你完整的写一套代码时,却无逻辑与框架可言。这说明编程还处于比较低的水平,你需要学会一种好的编程框架或者一种编程思想!比如模块化编程、状态机编程、分层思想
    的头像 发表于 07-27 11:23 2w次阅读
    什么是<b class='flag-5'>状态机</b>?<b class='flag-5'>状态机</b>5要素

    状态模式(状态机)

    以前写状态机,比较常用的方式是用 if-else 或 switch-case,高级的一点是函数指针列表。最近,看了一文章《c语言设计模式–状态模式(状态机)》(来源:embed linux
    发表于 12-16 16:53 7次下载
    <b class='flag-5'>状态</b>模式(<b class='flag-5'>状态机</b>)

    什么是状态机状态机的种类与实现

    状态机,又称有限状态机(Finite State Machine,FSM)或米利状态机(Mealy Machine),是一种描述系统状态变化的模型。在芯片设计中,
    的头像 发表于 10-19 10:27 6601次阅读