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

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

3天内不再提示

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

OSC开源社区 来源:OSCHINA 社区 2023-12-26 09:39 次阅读

作者:京东云开发者-京东科技孙扬威

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

1. 状态模式

状态模式的定义如下:

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

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

下面是状态模式的类图:

e0e5e6a8-a31b-11ee-8b88-92fbcf53809c.png

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

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

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

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

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

抽象状态类:

/**
 * @description: 抽象状态类
 */
publicabstractclassMyState{
    abstractvoidhandler();
}

具体状态类 A

/**
 * @description: 具体状态A
 */
publicclassRedLightStateextendsMyState{

    @Override
    voidhandler(){
        System.out.println("红灯停");
    }
}

具体状态类 B

/**
 * @description: 具体状态B
 */
publicclassGreenLightStateextendsMyState{

    @Override
    voidhandler(){
        System.out.println("绿灯行");
    }
}

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

/**
 * @description: 环境类
 */
publicclassMyContext{

    privateMyState state;

    publicvoidsetState(MyState state){
        this.state = state;
    }

    publicvoidhandler(){
        state.handler();
    }
}

测试类

/**
 * @description: 测试状态模式
 */
publicclassTestStateModel{
    publicstaticvoidmain(String[] args){
        MyContext myContext =newMyContext();

        RedLightState redLightState =newRedLightState();
        GreenLightState greenLightState =newGreenLightState();

        myContext.setState(redLightState);
        myContext.handler();//红灯停

        myContext.setState(greenLightState);
        myContext.handler();//绿灯行
    }
}

下面是对应的执行结果

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

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
publicclassOrder{
    privateLong orderId;
    privateOrderStatusEnum orderStatus;
}

/**
 * @description: 订单状态
 */
publicenumOrderStatusEnum{
    // 待支付
    WAIT_PAYMENT,
    // 待发货
    WAIT_DELIVER,
    // 待收货
    WAIT_RECEIVE,
    // 完成
    FINISH;
}

/**
 * @description:订单状态转换行为
 */
publicenumOrderStatusChangeEventEnum{
    //支付
    PAYED,
    //发货
    DELIVERY,
    //收货
    RECEIVED;
}

3.2 构造订单状态机

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

/**
 * @description: 订单状态机
 */
@Configuration
@EnableStateMachine
publicclassOrderStatusMachineConfigextendsStateMachineConfigurerAdapter {

    /**
     * 配置状态
     */
    @Override
    publicvoidconfigure(StateMachineStateConfigurer states)throwsException{
        states.withStates()
                .initial(OrderStatusEnum.WAIT_PAYMENT)
                .end(OrderStatusEnum.FINISH)
                .states(EnumSet.allOf(OrderStatusEnum.class));
    }

    /**
     * 配置状态转换事件关系
     */
    @Override
    publicvoidconfigure(StateMachineTransitionConfigurer transitions)throwsException{
        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
publicclassOrderStatusListener{
    @OnTransition(source ="WAIT_PAYMENT", target ="WAIT_DELIVER")
    publicbooleanpayTransition(Message message){
        Order order =(Order) message.getHeaders().get("order");
        order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);
        System.out.println("支付,状态机反馈信息:"+ message.getHeaders().toString());
        returntrue;
    }

    @OnTransition(source ="WAIT_DELIVER", target ="WAIT_RECEIVE")
    publicbooleandeliverTransition(Message message){
        Order order =(Order) message.getHeaders().get("order");
        order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);
        System.out.println("发货,状态机反馈信息:"+ message.getHeaders().toString());
        returntrue;
    }

    @OnTransition(source ="WAIT_RECEIVE", target ="FINISH")
    publicbooleanreceiveTransition(Message message){
        Order order =(Order) message.getHeaders().get("order");
        order.setOrderStatus(OrderStatusEnum.FINISH);
        System.out.println("收货,状态机反馈信息:"+ message.getHeaders().toString());
        returntrue;
    }

}

3.4 编写订单服务类

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

/**
 * @description: 订单服务
 */
@Service
publicclassOrderServiceImplimplementsOrderService{

    @Resource
    privateStateMachine orderStateMachine;

    privatelong id =1L;

    privateMap orders =Maps.newConcurrentMap();

    @Override
    publicOrdercreate(){
        Order order =newOrder();
        order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);
        order.setOrderId(id++);
        orders.put(order.getOrderId(), order);
        System.out.println("订单创建成功:"+ order.toString());
        return order;
    }

    @Override
    publicOrderpay(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
    publicOrderdeliver(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
    publicOrderreceive(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
    publicMap getOrders(){
        return orders;
    }

    /**
     * 发送状态转换事件
     * @param message
     * @return
     */
    privatesynchronizedbooleansendEvent(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
publicclassOrderController{

    @Resource
    privateOrderService orderService;

    @RequestMapping("/testOrderStatusChange")
    publicStringtestOrderStatusChange(){
        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";
    }

}
下面是对应的执行结果

e1062de6-a31b-11ee-8b88-92fbcf53809c.png 

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

4. 思考与总结

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

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

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

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

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

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

    关注

    33

    文章

    8474

    浏览量

    150768
  • API
    API
    +关注

    关注

    2

    文章

    1481

    浏览量

    61776
  • 状态机
    +关注

    关注

    2

    文章

    492

    浏览量

    27464
  • spring
    +关注

    关注

    0

    文章

    338

    浏览量

    14299
  • 设计模式
    +关注

    关注

    0

    文章

    53

    浏览量

    8620

原文标题:玩转Spring状态机

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    状态机编程实例-状态表法

    上篇文章,使用嵌套switch-case法的状态机编程,实现了一个炸弹拆除小游戏。本篇,继续介绍状态机编程的第二种方法状态表法,来
    的头像 发表于 06-20 09:05 1953次阅读
    <b class='flag-5'>状态机</b>编程实例-<b class='flag-5'>状态</b>表法

    状态机编程实例-面向对象的状态设计模式

    本编介绍了状态机编程的第3种方法——面向对象的状态设计模式,通过C++的继承特性,以及类指针,实现炸弹拆除小游戏中的状态机功能。
    的头像 发表于 06-28 09:04 1406次阅读
    <b class='flag-5'>状态机</b>编程实例-面向对象的<b class='flag-5'>状态</b>设计模式

    玩转Spring状态机

    说起Spring状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring
    的头像 发表于 06-25 14:21 870次阅读
    玩转<b class='flag-5'>Spring</b><b class='flag-5'>状态机</b>

    状态机原理及用法

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

    基于有限状态机的工控系统软件设计

    本文详 细论述了高速状态机的错步问题以及控制层中状态机状态划分问题,结合具体的应用实例,给出了基于状态机实现
    发表于 03-22 15:48 3次下载

    利用状态机状态机实现层次结构化设计

    练习九.利用状态机的嵌套实现层次结构化设计目的:1.运用主状态机与子状态机产生层次化的逻辑设计;
    发表于 02-11 05:52 3280次阅读
    利用<b class='flag-5'>状态机</b>的<b class='flag-5'>状态机</b><b class='flag-5'>实现</b>层次结构化设计

    基于FPGA实现状态机的设计

    状态机有三种描述方式:一段式状态机、两段式状态机、三段式状态机。下面就用一个小例子来看看三种方式是如何实现的。
    的头像 发表于 08-29 06:09 2802次阅读
    基于FPGA<b class='flag-5'>实现状态机</b>的设计

    使用函数指针的方法实现状态机

    之前写过一篇状态机的实用文章,很多朋友说有几个地方有点难度不易理解,今天给大家换种简单写法,使用函数指针的方法实现状态机状态机简介 有限状态机
    的头像 发表于 10-19 09:36 2372次阅读
    使用函数指针的<b class='flag-5'>方法</b><b class='flag-5'>实现状态机</b>

    FPGA:状态机简述

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

    Verilog设计过程中状态机的设计方法

    “本文主要分享了在Verilog设计过程中状态机的一些设计方法。 关于状态机 状态机本质是对具有逻辑顺序或时序顺序事件的一种描述方法,也就是
    的头像 发表于 06-25 11:04 2531次阅读

    LABVIEW的状态机实现资料合集

    LABVIEW的状态机实现资料合集
    发表于 01-04 11:18 45次下载

    状态机实现哪些内容

    状态机模式是一种行为模式,通过多态实现不同状态的调转行为的确是一种很好的方法,只可惜在嵌入式环境下,有时只能写纯C代码,并且还需要考虑代码的重入和多任务请求跳转等情形,因此
    的头像 发表于 06-22 14:26 702次阅读
    <b class='flag-5'>状态机</b>要<b class='flag-5'>实现</b>哪些内容

    如何在FPGA中实现状态机

    状态机往往是FPGA 开发的主力。选择合适的架构和实现方法将确保您获得一款最佳解决方案。 FPGA 常常用于执行基于序列和控制的行动, 比如实现一个简单的通信协议。对于设计人员来说,满
    的头像 发表于 07-18 16:05 1038次阅读
    如何在FPGA中<b class='flag-5'>实现状态机</b>

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

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

    如何在FPGA中实现状态机

    在FPGA(现场可编程门阵列)中实现状态机是一种常见的做法,用于控制复杂的数字系统行为。状态机能够根据当前的输入和系统状态,决定下一步的动作和新的状态。这里,我们将详细探讨如何在FPG
    的头像 发表于 07-18 15:57 459次阅读