JAVA-stateless4j StateMachine从入门到实战

简介: JAVA-stateless4j StateMachine从入门到实战

1、入门

1.1、为什么引入有限状态机?

最近做一个项目,项目中很多实体(Entity),每个实体都有很多状态(State),各状态会经过不同事件(Event)触发后转换到另一个状态。这些事件包括但不限于:用户页面点击触发,生效时间或失效时间到达,其他依赖实体状态变更等。在状态变更后还会有一系列动作(Action)处理一旦相互依赖实体或实体本身状态增多,状态转换变多,处理这些状态的业务代码也会分散在各处,代码处理很容易漏掉,维护成本很高。所以考虑引入有限状态机。

1.2、什么是有限状态机?

有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。任何一个FSM都可以用状态转换图来描述,图中的节点表示FSM中的一个状态,有向(方向表示从一个初态转换到次态)加权(权表示事件)边表示输入字符时状态的变化。如果图中不存在与当前状态与输入字符对应的有向边,则FSM将进入“消亡状态(Doom State)”,此后FSM将一直保持“消亡状态”。状态转换图中还有两个特殊状态:状态1称为“起始状态”,表示FSM的初始状态。状态6称为“结束状态”。

在启动一个FSM时,首先必须将FSM置于“起始状态”,然后触发一系列时间,最终,FSM会到达“结束状态”或者“消亡状态”。

说明:

在通常的FSM模型中,一般还存在一个“接受状态”,并且FSM可以从“接受状态”转换到另一个状态,只有在识别最后一个字符后,才会根据最终状态来决定是否接受所输入的字符串。此外,也可以将“起始状态”也作为接受状态,因此空的输入序列也是可以接受的。

TCP协议中的有限状态机

1.3、状态机要素

状态机可归纳为4个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:

①现态:是指当前所处的状态。

②条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。

③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

我们可以用状态表了表示整个过程,如下图所示。

2. 有限状态机表示方法

①状态转换图,例如图1

②状态转换表,例如图2

2、实战

有限状态机实现方式

2.1. Java switch case 或者 Scala 模式匹配实现
2.2、Java枚举实现

Java枚举实现有限状态机

2.3、设计模式:状态模式实现
2.4、使用stateless4j实现

github地址

使用浅析1

使用浅析2

使用浅析3

2.5、Spring Statemachine实现

github地址

2.6、Akka FSM 状态机实现

github地址

2.7、通过squirrel-foundation实现

github地址

自己做了些实例及封装

3、在项目中的应用

3.1 状态机在商品状态的转换

业务场景:

/**
 * 商品审核状态枚举
 */
public enum GoodsAuditStateEnum {
    // 商品审核状态
    NORMAL(0, "正常"),
    IN_CREATE(1, "申请新增中"),
    IN_UPDATE(2, "申请变更中"),
    IN_DELETE(3, "申请删除中"),
    IN_ON_SHELF(4, "申请上架中"),
    IN_UNDER_SHELF(5, "申请下架中"),
    IN_FROZEN(6, "申请冻结中"),
    IN_UNFREEZE(7, "申请解冻中");
}
/**
 * 触发动作
 */
 private enum Trigger {
     CREATE,
     UPDATE,
     DELETE,
     ON_SHELF,
     UNDER_SHELF,
     FROZEN,
     UNFREEZE,
     PASS,
     REJECT,
     REVOKE
 }
package cn.gov.zcy.service.agreement.item.fsm;
import cn.gov.zcy.service.agreement.item.enums.GoodsAuditStateEnum;
import com.googlecode.stateless4j.StateMachine;
import io.terminus.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
/**
 * 商品审核状态机
 */
@Slf4j
public class GoodsAuditStateFSM {
  private StateMachine<GoodsAuditStateEnum, GoodsAuditStateFSM.Trigger> stateMachine;
  private GoodsAuditStateFSM(GoodsAuditStateEnum state) throws Exception {
        this.stateMachine = new StateMachine<>(state);
    stateMachine.Configure(GoodsAuditStateEnum.IN_CREATE).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.IN_UPDATE).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.IN_DELETE).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.IN_ON_SHELF).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.IN_UNDER_SHELF).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.IN_FROZEN).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.IN_UNFREEZE).Permit(Trigger.PASS, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REJECT, GoodsAuditStateEnum.NORMAL).Permit(Trigger.REVOKE, GoodsAuditStateEnum.NORMAL);
        stateMachine.Configure(GoodsAuditStateEnum.NORMAL)
                .Permit(Trigger.CREATE, GoodsAuditStateEnum.IN_CREATE)
                .Permit(Trigger.UPDATE, GoodsAuditStateEnum.IN_UPDATE)
                .Permit(Trigger.DELETE, GoodsAuditStateEnum.IN_DELETE)
                .Permit(Trigger.ON_SHELF, GoodsAuditStateEnum.IN_ON_SHELF)
                .Permit(Trigger.UNDER_SHELF, GoodsAuditStateEnum.IN_UNDER_SHELF)
                .Permit(Trigger.FROZEN, GoodsAuditStateEnum.IN_FROZEN)
                .Permit(Trigger.UNFREEZE, GoodsAuditStateEnum.IN_UNFREEZE);
    }
}
import com.github.oxo42.stateless4j.StateMachine;
import org.junit.Test;
/**
 * Created by qiwenjie on 2021/06/30.
 *
 * 状态机测试类
 */
public class RunStateMachine {
    private static StateMachine<CurrentState,Trigger> stateMachine = new StateMachine<CurrentState, Trigger>(CurrentState.SMALL,GoodsAuditStateFSM.config);
    @Test
    public void testStateMachine(){
        stateMachine.Fire(Trigger.UPDATE);
        System.out.println("currentState-->"+stateMachine.getState());
    }
}
3.2 状态机在品牌状态的转换

业务场景:

/**
* 品牌审核状态
*/
public enum BrandAuditStatus {
    AUDIT_FAILED(-1, "审核不通过"),
    WAIT_SUBMIT(0, "待提交"),
    WAIT_AUDIT(1, "待审核"),
    AUDIT_PASSED(3, "审核通过");
}
/**
 * 品牌审核动作枚举
 */
public enum BrandAuditTrigger {
    /**
     * 提交审核
     */
    SUBMIT_AUDIT(0, "提交审核"),
    /**
     * 审核通过
     */
    AUDIT_APPROVE(1, "审核通过"),
    /**
     * 审核不通过
     */
    AUDIT_REJECT(2, "审核不通过"),
    /** 审核不通过再次提交 */
    SUBMIT_AUDIT_AGAIN(3, "审核不通过再次提交");
}
package cn.gov.zcy.audit.internal;
import com.googlecode.stateless4j.StateMachine;
import cn.gov.zcy.audit.enums.BrandAuditStatus;
import cn.gov.zcy.audit.enums.BrandAuditTrigger;
import lombok.extern.slf4j.Slf4j;
/**
* 注册trigger
*/
@Slf4j
public class BrandAuditFSM {
    private final StateMachine<BrandAuditStatus, BrandAuditTrigger> stateMachine;
    public BrandAuditFSM(BrandAuditStatus status) {
        this.stateMachine = new StateMachine<>(status);
        /**
         * [待提交]----------[提交审核]----------[待审核]
         */
        stateMachine.Configure(BrandAuditStatus.WAIT_SUBMIT)
                .Permit(BrandAuditTrigger.SUBMIT_AUDIT, BrandAuditStatus.WAIT_AUDIT);
        /**
         * [待审核]----------[审核通过]----------[审核通过]
         *
         * [待审核]----------[审核不通过]----------[审核不通过]
         */
        stateMachine.Configure(BrandAuditStatus.WAIT_AUDIT)
                .Permit(BrandAuditTrigger.AUDIT_APPROVE,BrandAuditStatus.AUDIT_PASSED)
                .Permit(BrandAuditTrigger.AUDIT_REJECT,BrandAuditStatus.AUDIT_FAILED);
        /**
         * [审核不通过]----------[不通过再次修改]----------[待提交]
         */
        stateMachine.Configure(BrandAuditStatus.AUDIT_FAILED)
                .Permit(BrandAuditTrigger.SUBMIT_AUDIT_AGAIN, BrandAuditStatus.WAIT_SUBMIT);
    }
import com.github.oxo42.stateless4j.StateMachine;
import org.junit.Test;
/**
 * Created by qiwenjie on 2021/06/30.
 *
 * 状态机测试类
 */
public class RunStateMachine {
    private static StateMachine<CurrentState,Trigger> stateMachine = new StateMachine<CurrentState, Trigger>(CurrentState.SMALL,BrandAuditFSM.config);
    @Test
    public void testStateMachine(){
        stateMachine.Fire(BrandAuditTrigger.AUDIT_APPROVE);
        System.out.println("currentState-->"+stateMachine.getState());
    }
}
3.3 状态机在SPU状态的转换

业务场景

/**
 * spu审核状态
 */
public enum SpuAuditStatus {
    AUDIT_FAILED(-1, "审核不通过"),
    WAIT_SUBMIT(0, "待提交"),
    WAIT_AUDIT(1, "待审核"),
    AUDIT_PASSED(3, "审核通过");
}
/**
 * spu审核动作枚举
 */
public enum SpuAuditTrigger {
    /**
     * 提交审核
     */
    SUBMIT_AUDIT(0, "提交审核"),
    /**
     * 审核通过
     */
    AUDIT_APPROVE(1, "审核通过"),
    /**
     * 审核不通过
     */
    AUDIT_REJECT(2, "审核不通过");
}
package cn.gov.zcy.audit.internal;
import cn.gov.zcy.audit.enums.SpuAuditStatus;
import cn.gov.zcy.audit.enums.SpuAuditTrigger;
import com.googlecode.stateless4j.StateMachine;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SpuAuditFSM {
    private final StateMachine<SpuAuditStatus, SpuAuditTrigger> stateMachine;
    public SpuAuditFSM(SpuAuditStatus status) throws Exception {
        this.stateMachine = new StateMachine<>(status);
        /**
         * [待提交]----------[提交审核]----------[待审核]
         */
       stateMachine.Configure(SpuAuditStatus.WAIT_SUBMIT).Permit(SpuAuditTrigger.SUBMIT_AUDIT,
            SpuAuditStatus.WAIT_AUDIT);
        /**
         * [待审核]----------[审核通过]----------[审核通过]
         *
         * [待审核]----------[审核被拒]----------[审核不通过]
         */
        stateMachine.Configure(SpuAuditStatus.WAIT_AUDIT)
            .Permit(SpuAuditTrigger.AUDIT_APPROVE, SpuAuditStatus.AUDIT_PASSED)
            .Permit(SpuAuditTrigger.AUDIT_REJECT, SpuAuditStatus.AUDIT_FAILED);
    }
}
import com.github.oxo42.stateless4j.StateMachine;
import org.junit.Test;
/**
 * Created by qiwenjie on 2021/06/30.
 *
 * 状态机测试类
 */
public class RunStateMachine {
    private static StateMachine<CurrentState,Trigger> stateMachine = new StateMachine<CurrentState, Trigger>(CurrentState.SMALL,SpuAuditFSM.config);
    @Test
    public void testStateMachine(){
        stateMachine.Fire(SpuAuditTrigger.AUDIT_APPROVE);
        System.out.println("currentState-->"+stateMachine.getState());
    }
}

自律是解决人生问题最主要的工具,也是消除人生痛苦最重要的方法

相关文章
|
1月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
50 3
|
1月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
61 2
|
7天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
1月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
18天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
24天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
64 5
|
21天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
32 1
|
27天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
44 3
|
29天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
下一篇
无影云桌面