一个轻量实用的Java状态机框架--Cola-StateMachine

这篇具有很好参考价值的文章主要介绍了一个轻量实用的Java状态机框架--Cola-StateMachine。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

状态机

状态机是一种描述系统行为的工具,通过定义一组状态和状态转换规则,可以模拟和控制系统的状态变化。在软件工程中,状态机被广泛应用于实现系统的行为和流程控制,特别是在处理业务流程、游戏逻辑或并发程序时。状态机可以用于控制系统的不同状态,以及状态之间的转换条件和转换逻辑。

一般来说,状态机由状态、转移和事件三个基本组成部分。状态是系统可能处于的不同情况,转移定义了状态之间的关系和切换条件,事件则是触发状态转换的因素。通过定义状态和转移,状态机能够将复杂系统的行为和状态分离,实现清晰的逻辑分离和模块化。

在实际应用中,状态机的使用可以提高代码的可读性和可维护性,降低系统的复杂度,方便对系统行为进行建模和控制。同时,状态机的设计也可以帮助开发者更好地理解和组织系统流程,简化开发过程。

下面是状态机的一些典型应用

  1. 业务流程控制:状态机可以用于描述复杂的业务流程,将业务逻辑分解为一系列离散的状态和转换规则,使代码更加清晰、可维护和可扩展。例如,在电商系统中,订单状态机可以包含多种状态(已创建、已支付、已发货等),并定义了各个状态之间的转换规则和处理逻辑。

  2. UI交互设计:状态机可以用于描述UI界面的各种状态和用户操作之间的关系,例如表单验证、导航菜单、游戏场景等,通过状态图来设计和呈现UI界面的交互逻辑,提高用户体验和操作效率。

  3. 并发编程:状态机可以用于实现复杂的并发控制逻辑,例如多线程调度、协程管理等,通过状态和转移规则来控制不同任务之间的切换和执行顺序,提高程序的可伸缩性和性能。

状态机选型

状态机的主要选型有两个,一个是Spring Statemachine,一个是Squirrel statemachine。他们的优点是功能很完备,缺点也是功能很完备,使用起来比较复杂,并且是非线程安全。

在正常的中小型项目,其实是不需要用到那么多状态机的高级玩法:比如状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。所以,这时候我们就需要一个轻量级、高性能的状态机框架。这就是我们今天的主角:Cola-StateMachine 。

Cola-StateMachine 是阿里云团队开源的一款轻量级、高性能的状态机框架,用于解决业务流程中状态流转的控制问题。它基于无状态化设计,支持事件驱动和分布式事务,适用于构建复杂且高并发的状态机模型。它的主要特点是无状态、采用纯Java实现,使用Fluent Interface(连贯接口)来定义状态和事件,适用于管理状态转换的场景,如订单状态、支付状态等简单有限状态场景。Cola-StateMachine可以帮助开发者方便地管理业务对象的状态转换,提高开发效率。

可以说,Cola-StateMachine 很适合我们做电商项目使用

项目地址:项目链接

Cola-StateMachine 核心概念

State:状态
Event:事件,状态由事件触发,引起变化
Transition:流转,表示从一个状态到另一个状态
External Transition:外部流转,两个不同状态之间的流转
Internal Transition:内部流转,同一个状态之间的流转
Condition:条件,表示是否允许到达某个状态
Action:动作,到达某个状态之后,可以做什么
StateMachine:状态机

cola状态机,springboot,java,后端,状态机,spring boot
具体来说,Cola-StateMachine的流转模型可以通过以下步骤实现:

  1. 定义状态:使用连贯接口定义系统的不同状态,每个状态可以通过类或接口来表示。
  2. 定义事件:定义触发状态转换的事件,每个事件可以通过类或接口来表示。
  3. 定义转移:根据业务规则定义状态之间的关系和转换条件。在Cola-StateMachine中,可以使用transition()方法来定义转移,指定起始状态、结束状态、事件和条件等参数。
  4. 定义条件:使用condition()方法来定义是否允许到达某个状态。条件可以是一个布尔表达式或一个返回布尔值的函数。
  5. 定义动作:在状态转换过程中,可以定义一些动作来执行特定的操作。使用action()方法来定义动作,指定一个执行特定操作的函数或Lambda表达式。
  6. 创建状态机实例:根据定义的流转模型,创建Cola-StateMachine的实例。通过实例化状态机对象,可以控制系统的状态转换和行为。
  7. 触发状态转换:通过调用状态机实例的方法来触发状态转换。根据事件的类型和条件,状态机会自动执行相应的动作并更新系统的状态。

通过以上步骤,Cola-StateMachine的流转模型可以帮助开发者实现清晰的状态转换逻辑和控制流程。

Cola-StateMachine的集成与使用

我们先来看一下Cola-StateMachine的状态机接口定义

public interface StateMachine<S, E, C> extends Visitable{

    /**
     * Verify if an event {@code E} can be fired from current state {@code S}
     * @param sourceStateId
     * @param event
     * @return
     */
    boolean verify(S sourceStateId,E event);

    /**
     * Send an event {@code E} to the state machine.
     *
     * @param sourceState the source state
     * @param event the event to send
     * @param ctx the user defined context
     * @return the target state
     */
     S fireEvent(S sourceState, E event, C ctx);

    /**
     * MachineId is the identifier for a State Machine
     * @return
     */
    String getMachineId();

    /**
     * Use visitor pattern to display the structure of the state machine
     */
    void showStateMachine();

    String generatePlantUML();
}

先,泛型机接口声明了三个泛型类,S表示状态类型,E表示事件类型,C表示上下文类型

接口定义了以下方法:

  1. verify(S sourceStateId, E event):验证当前状态是否可以接收指定事件,返回布尔值。该方法是状态机进行状态转换前的预处理过程,可以根据业务规则和当前状态来判断事件是否合法。

  2. fireEvent(S sourceState, E event, C ctx):将指定事件发送给状态机,触发状态转换,并返回目标状态。该方法是状态机进行状态转换的核心逻辑,可以根据当前状态和事件来计算出目标状态,并执行相应的动作或逻辑。

  3. getMachineId():获取状态机的唯一标识符。该方法可以用于区分不同的状态机实例,方便进行管理和监控。

  4. showStateMachine():使用访问者模式展示状态机的结构。该方法可以将状态机的状态、事件、转移规则等信息以图形化形式展示出来,方便理解和调试。

  5. generatePlantUML():生成PlantUML格式的状态机图。该方法可以将状态机的信息以PlantUML语言的形式输出,方便进行文档记录和分享。

下面我就以订单状态为例,使用cola-statemachine实现一个状态机

1、添加依赖,Spring Boot 项目依赖如下

      <dependency>
            <groupId>com.alibaba.cola</groupId>
            <artifactId>cola-component-statemachine</artifactId>
            <version>4.4.0-SNAPSHOT</version>
        </dependency>

2、定义状态

我们创建一个枚举类来给我们的订单定义状态

@Getter
public enum OrderStatusEnum {
    INITIAL(0,"初始状态"),
    PAY_PENDING(1,"待支付"),
    PAY_FAILED(3,"支付失败"),
    PAY_SUCCESS(4,"已支付"),
    CANCELED("已取消"),
    ;

    private int statusCode;

    private String statusDesc;


    OrderStatusEnum(int statusCode, String statusDesc) {
        this.statusCode = statusCode;
        this.statusDesc = statusDesc;
    }
}

3、定义事件

同样的,我们用枚举来定义一个事件枚举类

/**
 * 订单事件枚举类
 */
@Getter
public enum  OrderEventEnum {

    CreateOrder, // 创建订单
    PAYING,//支付确认
    PAY_SUCCESS,//支付成功
    PAY_FAIL//支付失败
    ;

}

4、定义上下文

在状态机接口中有一个泛型C用来表示状态机的上下文信息,使用上下文信息,我们可以在状态转换过程中传递一些额外的数据和参数,方便进行状态机的状态转换和业务逻辑的处理。

上下文类可以帮我们传递参数,比如下面的上下文可以帮我们传递订单ID和金额

/**
 * 上下文信息类
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class OrderContext {

    /**
     * 订单ID
     */
    private String orderId;


    /**
     * 订单金额
     */
    private Integer payAmount;
    
}

5、定义状态机处理器

在我们构造状态机的转移规则时,需要使用到两个方法,一个过滤条件方法when(Condition condition),一个是转换过程中要执行的动作perform(Action<S, E, C> action);

这两个方法需要我们提供一个Condition condition和Action<S, E, C> action,为了方便管理和整洁,我们可以定义一个接口,然后定义两个方法提供这两个参数

下面,我就定义一个处理器接口,定义一个 condition()方法用于返回条件对象, action()用于我们执行的动作

状态机处理器接口如下

/**
 * 处理器接口,用于定义状态扭转的条件和执行的动作
 * @param <S>
 * @param <E>
 * @param <C>
 */
public interface StateMachineHandler <S,E,C>{

    /**
     * 过滤条件
     * @return
     */
    Condition<C> condition();

    /**
     * 执行动作
     * @return
     */
    Action<S, E, C> action();

}

condition():在状态机处理过程中,当某个事件发生并试图进行状态转移时,会先检查当前条件是否满足。条件通常根据上下文信息来决定是否允许状态转换。条件(Condition)对象表示在当前状态和事件下是否应该执行该处理器。

Condition 是一个泛型接口,其中的类型参数 C 表示上下文类型。

public interface Condition<C> {

    /**
     * @param context context object
     * @return whether the context satisfied current condition
     */
    boolean isSatisfied(C context);

    default String name(){
        return this.getClass().getSimpleName();
    }
}

Condition 接口定义了一个 isSatisfied() 方法,该方法接收一个上下文对象,并返回一个布尔值,表示该上下文对象是否满足当前条件,符合返回true

action(): 当状态转移被允许后,该动作将会被执行。动作通常定义了状态转移前后需要执行的操作,比如数据库更新、发送消息等。如果返回值为 null,则表示不需要执行任何操作。

public interface Action<S, E, C> {

    public void execute(S from, S to, E event, C context);

}

Action 是一个泛型接口,其中的类型参数 S 表示状态类型,E 表示事件类型,C 表示上下文类型。

Action 接口定义了一个 execute() 方法,该方法接收起始状态 from、目标状态 to、触发事件 event 和上下文对象 context,用于执行相应的动作。

type(): 表示这个处理器的类型或标识。这可以用于区分不同的处理器,并在状态机配置中引用它们。

下面我创建一个由创始状态到待支付的创建订单事件处理器

/**
 * 创建订单的处理器
 */
@Component
public class CreateOrderHandler implements StateMachineHandler<OrderStatusEnum, OrderEventEnum, OrderContext> {


    @Override
    public Condition<OrderContext> condition() {
        //进行条件判断,这里是通过上下文获取订单金额,订单金额大于0就符合条件
        return (context -> {return context.getPayAmount()>0;});
    }


    @Override
    public Action<OrderStatusEnum, OrderEventEnum, OrderContext> action() {
        //执行逻辑操作
        return (from,to,event,context)->{
            //生成订单等操作,具体逻辑大家按实际情况实现
        };
    }
}


6、状态机配置,构建状态扭转规则
配置不同状态下对不同事件的响应,也就是如何从一个状态转移到另一个状态

我们通过在配置类中配置StateMachine来实现,下面是一个简单的StateMachine配置类,我只配置了创建订单的状态转换,还有其他状态转换,可以按照创建订单这个模式进行配置

@Slf4j
@Configuration
public class StateMachineConfig {

    /**
     * 状态机ID
     */
    private static final String MACHINE_ID = "Order_StateMachine";
    
    @Autowired
    private CreateOrderHandler createOrderHandler;

    /**
     * 状态机配置
     * @return
     */
    @Bean
    public StateMachine<OrderStatusEnum,OrderEventEnum,OrderContext> stateMachine(){
        //创建 一个StateMachineBuilder 实例,用于构建和配置状态机
        StateMachineBuilder<OrderStatusEnum,OrderEventEnum,OrderContext> builder = StateMachineBuilderFactory.create();

        //创建订单事件
        builder.externalTransition()
                .from(OrderStatusEnum.INITIAL)
                .to(OrderStatusEnum.PAY_PENDING)
                .on(OrderEventEnum.CreateOrder)
                .when(createOrderHandler.condition())
                .perform(createOrderHandler.action());

        //创建状态机
        StateMachine<OrderStatusEnum, OrderEventEnum, OrderContext> build = builder.build(MACHINE_ID);
        return build;
    }

}

配置解析:

  1. 通过 StateMachineBuilderFactory.create() 方法创建了一个 StateMachineBuilder 的实例,并使用泛型参数 <OrderStatusEnum,OrderEventEnum,OrderContext> 指定了状态、事件和上下文类型。

  2. 使用 builder.externalTransition() 方法开始配置一个外部转移(external transition),表示在接收到特定事件时从一个状态转移到另一个状态。

  3. 通过 .from(OrderStatusEnum.INITIAL) 指定了转移的起始状态为OrderStatusEnum.INITIAL。

有时候,可能会有多个起始状态,这就要使用fromAmong方法,例如.fromAmong(OrderStatusEnum.INITIAL,OrderStatusEnum.PAY_PENDING)

  1. 通过 .to(OrderStatusEnum.PAY_PENDING) 指定了转移的目标状态为 OrderStatusEnum.PAY_PENDING。

  2. 通过 .on(OrderEventEnum.CreateOrder) 指定了触发转移的事件为 OrderEventEnum.CreateOrder。

  3. 通过 .when(createOrderHandler.condition()) 指定了执行该转移的条件。代码中使用createOrderHandler的 condition() 方法获取条件对象。

  4. 通过 .perform(createOrderHandler.action()) 指定了转移时要执行的动作。使用 createOrderHandler的 action() 方法获取动作对象。

总结一下:from指定起始状态,to指定目标状态,on指定触发事件,when指定转移条件,perform指定状态转移时执行的动作

内部状态扭转

除了外部状态流转,状态机还有一种内部状态流转

内部状态流转是指当一个事件发生时,状态机并不切换到新的状态,而是在当前状态下执行一些操作或者更新内部状态,但保持在同一个状态内。这种情况下,尽管状态看起来没有改变,但实际上可能进行了某种处理或者完成了某些业务逻辑。

内部状态流转的配置如下

builder.internalTransition()
    .within(OrderState.WAIT_PAYMENT)
    .on(OrderStatusChangeEvent.PAYED)
    .when(checkCondition())
    .perform(doAction());

可以看到内部流转没有目标状态,只有一个within的内部流转状态和一个on触发事件

上面例子的内部转换发生在WAIT_PAYMENT状态下,当发生PAYED事件时执行状态流转,当满足checkCondition()时,执行doAction操作,执行成功则返回状态WAIT_PAYMENT,即状态没有发生改变,只是执行了某些操作

7、 发送事件

当我们触发一个事件并想执行相应的状态转换时,就可以使用fireEvent方法进行操作

下面是代码示例

        //构造上下文对象
        OrderContext orderContext =  new OrderContext();;
        //根据状态机ID获取状态机
        StateMachine<OrderStatusEnum, OrderEventEnum, OrderContext> stateMachine =
                StateMachineFactory.get(MACHINE_ID);
        //将指定事件发送给状态机,第一个参数为当前状态,第二个参数为触发事件,第三个参数为上下文对象
       OrderStatusEnum resultOrderStatusEnum = stateMachine.fireEvent(OrderStatusEnum.INITIAL, OrderEventEnum.CreateOrder, orderContext);
        // 根据返回的目标状态进行相应操作
        if (resultOrderStatusEnum == OrderStatusEnum.PAY_PENDING) {
            // 状态转换成功后的其他业务逻辑处理
        } else {
            // 异常的状态转换处理逻辑
        }

StateMachine的fireEvent()方法用于触发状态机上的事件并执行相应的状态转换。这个方法通常接收三个参数:

sourceState: 当前的状态(源状态)。
event: 需要触发的事件。
context: 用户自定义的上下文对象,可能包含与当前状态或事件关联的数据。文章来源地址https://www.toymoban.com/news/detail-856259.html

到了这里,关于一个轻量实用的Java状态机框架--Cola-StateMachine的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用go设计开发一个自己的轻量级登录库/框架吧

    几乎每个项目都会有登录,退出等用户功能,而登录又不单仅仅是登录,我们要考虑很多东西。 token该怎么生成?生成什么样的? 是在Cookie存token还是请求头存token?读取的时候怎么读取? 允许同一个账号被多次登录吗?多次登录他们的token是一样的?还是不一样的? 登录也

    2024年02月03日
    浏览(52)
  • 用go设计开发一个自己的轻量级登录库/框架吧(业务篇)

    本篇会讲讲框架的登录业务的实现。实现三种登录模式: 同一用户只能登录一次 同一用户多次登录多token 同一用户多次登录共享一个token 源码:weloe/token-go: a light login library (github.com) 首先从我们要考虑是底层该怎么存储登录信息来去达成这三种登录模式 同一用户只能登录一

    2024年02月04日
    浏览(53)
  • 用go设计开发一个自己的轻量级登录库/框架吧(拓展篇)

    主库:weloe/token-go: a light login library. 扩展库:weloe/token-go-extensions (github.com) 本篇给主库扩展一个Adapter提供简单的外部数据存储。 一个库/框架往往不能完成所有事情,需要其他库/框架的支持才能达到更加完善的效果。本篇会对token-go框架的Adapter进行简单的拓展。 首先我们应

    2024年02月05日
    浏览(68)
  • 用go设计开发一个自己的轻量级登录库/框架吧(项目维护篇)

    本篇将开始讲讲开发库/框架的最开始阶段,也就是搭建一个项目 源码:weloe/token-go: a light login library (github.com) 项目结构,不是上一篇所说的代码架构,而是分包,明确的分包更有助于我们的开发。本框架分包如下 我们选择使用GitHub进行代码托管,同时也使用GitHub Actions进行

    2024年02月03日
    浏览(50)
  • 我的Qt作品(18)模仿Qt Creator IDE写了一个轻量级的视觉框架

    Qt Creator的源码比较庞大。前几年我陆陆续续读过里面的源码。也写了几篇博文: https://blog.csdn.net/libaineu2004/article/details/104728857 https://blog.csdn.net/libaineu2004/article/details/89407333 最近一直想找机会,借用这个IDE的皮,来写一个视觉框架的小demo。 1、先来看看官方Qt Creator IDE的主界

    2023年04月24日
    浏览(37)
  • 架构:Oracle推出开源轻量级Java微服务框架Helidon

    在当今数字化时代,微服务架构已经成为构建灵活、可伸缩和高度可维护的应用程序的首选方式之一。Java一直以其稳定性和跨平台性而闻名,而Oracle则一直是Java的领先开发者之一。为了满足不断增长的微服务市场需求,Oracle于近年推出了开源的轻量级Java微服务框架Helidon,为

    2024年02月02日
    浏览(50)
  • ioGame21发布,史诗级增强,Java Netty 轻量级网络游戏服务器框架

    标题:ioGame21 发布:史诗级增强,Java Netty 轻量级网络游戏服务器框架 引言 近日,一款名为 ioGame21 的轻量级网络游戏服务器框架在 GitHub 上正式发布。这款框架基于 Java Netty 构建,具有高性能、高可扩展性和易于使用的特点。ioGame21 的发布将为游戏开发者提供一个强大的工

    2024年02月19日
    浏览(50)
  • 一个很好用且开源的java验证码框架kaptcha

             kaptcha是一个优秀的验证码框架 ,在不多的项目中使用了很多年很稳定,功能强大配置项很丰富,可以根据不同的需求配置不同的效果。  以下最常用的配置  字母+数字 验证码实现的效果图:    如果不是springboot项目,xml配置项:

    2024年04月12日
    浏览(39)
  • C++轻量级单元测试框架

    单元测试是构建稳定、高质量的程序、服务或系统的必不可少的一环。通过单元测试,我们可以在开发过程中及时发现和修复代码中的问题,提高代码的质量和可维护性。同时,单元测试也可以帮助我们更好地理解代码的功能和实现细节,从而更好地进行代码重构和优化。

    2023年04月25日
    浏览(72)
  • 轻量级Web框架Flask(二)

    MySQL是免费开源软件,大家可以自行搜索其官网(https://www.MySQL.com/downloads/) 测试MySQL是否安装成功 在所有程序中,找到MySQL→MySQL Server 5.6下面的命令行工具,然后单击输入密码后回车,就可以知道MySQL数据库是否链接成功。 右击桌面上的“计算机”,在弹出的快捷键菜单中

    2023年04月15日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包