spring-statemachine状态机梳理

news2024/12/29 9:41:18

目录

一、基本回顾

1、为什么要用状态机

2、什么是状态机

3、状态机可归纳为4个要素

4、对应Spring StateMachine的核心步骤

5、简单例子

添加maven依赖

定义状态枚举和事件枚举

完成状态机的配置

简单测试一下

添加Listener 监听器,当状态变更时,触发方法

添加拦截器

StateMachine 状态机实例

 定义一个基于状态机实例的Handler

Springboot注入Handler和Listener bean的Configuration类

​编辑 注解方式使用

多个状态机共存

6、适用场景 

二、测试注意


一、基本回顾

1、为什么要用状态机

系统状态和条件非常多、状态间切换复杂的场景,如何更好实现状态的切换:

方法1: if-else/switch方式实现,缺点是 代码可读性、可维护性差;

方式2:状态机实现。 本质上,状态机具备了多种优点,如 可读性好,可维护性高,以及容易扩展等等

2、什么是状态机

状态机(state machine)是一种行为,它指定对象在其生命周期内响应事件所经历的状态序列,以及对象对这些事件的响应

3、状态机可归纳为4个要素

即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。

现态:现在的状态。

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

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

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

4、对应Spring StateMachine的核心步骤

  • 定义状态枚举
  • 定义事件枚举
  • 定义状态机配置,设置初始状态,以及状态与事件之间的关系
  • 定义状态监听器,当状态变更时,触发方法

5、简单例子

添加maven依赖

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

定义状态枚举和事件枚举

public enum CompanyStatus {

    WAIT_SUBMIT(0, "待企业实名"),
    WAIT_REAL_NAME(1, "待个人实名"),
     。。。

CompanyStatus(int status, String desc) {
    this.status = status;
    this.desc = desc;
}
}

public enum CompanyEvents {
    UNKNOWN_EVENT(0, "未知事件"),
    UN_REAL_NAME(1, "个人未实名事件"),
    REAL_NAME(2, "个人实名事件"),
...

CompanyEvents(int value, String desc) {
    this.value = value;
    this.desc = desc;
}
}

完成状态机的配置

包括:(1)状态机的初始状态和所有状态;(2)状态之间的转移规则

@Configuration
@EnableStateMachineFactory(name = "companyStateMachineFactory")
public class CompanyStateMachineConfig extends EnumStateMachineConfigurerAdapter<CompanyStatus, CompanyEvents> {

    @Override
    public void configure(StateMachineStateConfigurer<CompanyStatus, CompanyEvents> states) throws Exception {
        states.withStates()
                // 定义初始状态
                .initial(CompanyStatus.WAIT_SUBMIT)
                // 定义所有状态集合
                .states(EnumSet.allOf(CompanyStatus.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<CompanyStatus, CompanyEvents> transitions) throws Exception {
        transitions.withExternal()
                .source(CompanyStatus.WAIT_SUBMIT).target(CompanyStatus.WAIT_REAL_NAME)
                .event(CompanyEvents.UN_REAL_NAME)
                .and().withExternal()
                .source(CompanyStatus.WAIT_SUBMIT).target(CompanyStatus.WAIT_CHECK)
                .event(CompanyEvents.COMPANY_SUBMITED)
                .and().withExternal()
...

}
}

简单测试一下

启动状态机、发送不同的事件,通过日志验证状态机的流转过程

@Resource
    StateMachine<CompanyStatus, CompanyEvents> stateMachine;
    public void createStateMachine(){
        stateMachine.start();
        stateMachine.sendEvent(CompanyEvents.UN_REAL_NAME);
        stateMachine.sendEvent(CompanyEvents.REAL_NAME);
    }

添加Listener 监听器,当状态变更时,触发方法

public interface PersistStateChangeListener {
    /**
     * 当状态被持久化,调用此方法
     *
     * @param state
     * @param message
     * @param transition
     * @param stateMachine 状态机实例
     */
    void onPersist(State<CompanyStatus, CompanyEvents> state, Message<CompanyEvents> message,
            Transition<CompanyStatus, CompanyEvents> transition, StateMachine<CompanyStatus, CompanyEvents> stateMachine);
}


@Slf4j
@Component("companyPersistStateChangeListener")
public class CompanyPersistStateChangeListener implements PersistStateChangeListener {
...真正实现

}

添加拦截器

不同于Listener。其可以改变状态转移链的变化。

主要在preEvent(事件预处理)、preStateChange(状态变更的前置处理)、postStateChange(状态变更的后置处理)、preTransition(转化的前置处理)、postTransition(转化的后置处理)、stateMachineError(异常处理)等执行点生效。

private class PersistingStateChangeInterceptor extends StateMachineInterceptorAdapter<CompanyStatus, CompanyEvents> {

        @Override
        public void preStateChange(State<CompanyStatus, CompanyEvents> state, Message<CompanyEvents> message,
                                   Transition<CompanyStatus, CompanyEvents> transition, StateMachine<CompanyStatus,
                CompanyEvents> stateMachine) {
            listeners.onPersist(state, message, transition, stateMachine);
        }
    }

StateMachine 状态机实例

StateMachine 状态机实例,spring statemachine支持单例、工厂模式两种方式创建。

每个statemachine有一个独有的machineId用于标识machine实例;

需要注意的是statemachine实例内部存储了当前状态机等上下文相关的属性,因此这个实例不能够被多线程共享。

例如:工厂模式创建实例

 定义一个基于状态机实例的Handler

为了方便扩展更多的Listener,以及管理Listeners和Interceptors。可以定义一个基于状态机实例的Handler: PersistStateMachineHandler,以及持久化实体的监听器。

Springboot注入Handler和Listener bean的Configuration类

 注解方式使用

@Slf4j
@Service
public class TestService {

@PostConstruct
private void initialize() {
    this.persistStateMachineHandler.addPersistStateChangeListener(companyPersistStateChangeListener);
}

public void userToDoSomething() {

...//userToDoSomething

persistStateMachineHandler.handleEventWithState(MessageBuilder.withPayload(events)
        .setHeader("company", company.getId()).build(), CompanyStatus.of(status));

}

}

多个状态机共存

在实际项目中一般都会有多个状态机并发执行,比如订单,同一时刻会有不止一个订单在运行,而每个订单都有自己的订单状态机流程。想要实现多个状态机的并行执行,就需要用到builder.

6、适用场景 

各种审核逻辑业务: 如 财务审核、交易业务、TOB结算业务审核、合同状态

订单支付类业务: 如 商家单据,订单会有多种状态:已下单、待支付、已支付、待发货、待收货、已完成、退款中、退款成功等等

二、测试注意

  • 单个业务场景下,状态流转正常;
  • 状态异常流转时兼容处理;
  • 并发操作,分别流转到不同状态正常

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

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

相关文章

1. SpringMVC概述与入门

1. SpringMVC简介 SpringMVC是一种基于Java实现MVC模型的轻量级Web框架优点 使用简单&#xff0c;开发便捷&#xff08;相比于Servlet&#xff09;灵活性强 2. 入门案例 2.1 实现步骤分析 1 创建web工程&#xff08;Maven结构&#xff09; 2 设置tomcat服务器&#xff0c;加…

Qt属性系统(Qt Property System)

Qt提供了巧妙的属性系统&#xff0c;它与某些编译器支持的属性系统相似。然而&#xff0c;作为平台和编译器无关的库&#xff0c;Qt不能够依赖于那些非标准的编译器特性&#xff0c;比如__property 或者 [property]。Qt的解决方案能够被任何Qt支持的平台下的标准C编译器支持。它…

Kafka工作流程简介

消息传递模式简介: 一个消息系统负责将数据从一个应用程序传递到另外一个应用程序中&#xff0c;应用程序只关注数据&#xff0c;无需关注数据在多个应用之间是如何传递的。 分布式消息传递基于可靠的消息队列&#xff0c;在客户端应用和消息系统之间异步传递消息。 消息传递有…

MySQL面试常问问题(日志) —— 赶快收藏

目录 1.MySQL日志文件有哪些&#xff1f;分别介绍下作用&#xff1f; 2.binlog和redo log有什么区别&#xff1f; 3.一条更新语句怎么执行的了解吗&#xff1f; 4.那为什么要两阶段提交呢&#xff1f; 5.redo log怎么刷入磁盘的知道吗&#xff1f; 1.MySQL日志文件有哪些&…

Typora配合PicGo阿里云图床配置

写博客的时候&#xff0c;刚开始直接在各大平台上直接写&#xff0c;后来还是觉得不太方便&#xff0c;需要在各大平台之间来回切换。于是就改用Typora&#xff0c;但是有个问题就是图片的处理&#xff0c;只能放在本地。想要发布到各大平台&#xff0c;就需要图床。本文结合阿…

2022年安徽最新水利水电施工安全员模拟试题及答案

百分百题库提供水利水电施工安全员考试试题、水利水电施工安全员考试预测题、水利水电施工安全员考试真题、水利水电施工安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 15.围堰工程可以直接判定为生产安全重大事故隐…

【UE4 第一人称射击游戏】02-玩家健康和护甲

步骤&#xff1a; 1.在“ThirdPersonCharacter”中添加两个浮点变量“Health”和“Armor” 将默认值设为1.0&#xff0c;表示默认100% 2.新建一个控件蓝图&#xff0c;命名为“FPSHUD” 打开“FPSHUD”&#xff0c;添加两个进度条&#xff0c;分别表示当前的生命值和护甲量 调…

InnoDB架构体系

2、InnoDB架构体系 2.1、内存结构 2.1.1、buffer pool InnoDB内存缓存区&#xff0c;使用空间换时间的思想&#xff0c;给数据做了一个缓存。把热点的数据存储在内存中&#xff0c;减少IO次数&#xff0c;提高效率。 show variables like %innodb_buffer_pool%;buffer pool …

基于polar码和SCMA的多用户检测的联合检测译码matlab仿真,polar采用SCAN软译码,SCMA用MPA算法

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 构造的核心是通过信道极化&#xff08;channel polarization&#xff09;处理&#xff0c;在编码侧采用方法使各个子信道呈现出不同的可靠性&#xff0c;当码长持续增加时&#xff0c;部分信道将…

【C语言】初阶习题

目录 1.图案问题 2、时分秒转换 3、打印1-100之间所有3的倍数的数字 4、打印100~200之间的素数 5、给定两个数&#xff0c;求这两个数的最大公约数 6、在屏幕上输出9*9乘法口诀表 7、计算1/1-1/21/3-1/41/5 …… 1/99 - 1/100 的值&#xff0c;打印出结果 8、二分查找 …

说明DBCO-PEG-SH二苯并环辛炔-聚乙二醇-巯基科研试剂材料,DBCO-PEG-SH结构式

结构式 英文&#xff1a;DBCO-PEG-SH&#xff0c;DBCO-PEG-Thiol 中文&#xff1a;二苯并环辛炔-聚乙二醇-巯基 溶剂&#xff1a;溶于水、DMSO等常规有机溶剂 性状&#xff1a;液体/固体白色或淡黄色粉末&#xff0c;取决于分子量 用途范围&#xff1a;广泛应用于医药、生物…

一文搞懂Linux下并制作环形缓冲区

1.环形缓冲区log_buf[]又是存在内核的哪个文件呢&#xff1f; 位于/proc/kmsg里,所以除了dmesg命令查看,也可以使用cat /proc/kmsg来查看 2.但是,dmesg命令和cat /proc/kmsg有所不同 2.1 dmesg命令 每次使用,都会打印出环形缓冲区的所有信息 2.2 cat /proc/kmsg 只会打印…

项目管理中,进度计划是摆设吗?

1、忽视进度计划 项目管理中&#xff0c;有的人认为进度计划是摆设&#xff0c;不重视计划&#xff0c;只是为了满足合同工期&#xff0c;做给客户看&#xff0c;因此草率的编制进度计划。 在通过客户要求后&#xff0c;就将计划搁置在一边&#xff0c;这就导致后期在执行任务…

科班演员陶奕菱亮相海南电影节:人生没有白走的路,每一步都算数

今天的记忆是带着海水的味道………”12月18日&#xff0c;在年末岁尾之际&#xff0c;徽风皖韵熏陶下成长的新生代演员陶奕菱再次来到海南三亚&#xff0c;受邀参加第四届海南岛国际电影节&#xff0c;迫不及待地跑向沙滩面朝大海&#xff0c;感受多姿多彩的魅力三亚。 优雅端…

Java对象内存布局和对象头

对象的内存布局 在HotSpot虚拟机里&#xff0c;对象在堆内存中的存储布局可以划分为三个部分&#xff1a;对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Data&#xff09;和对齐填充&#xff08;Padding&#xff09;。 对象头 对象头由对象标记Mark W…

进程间通信

进程间通信 进程间通信是不同进程之间的信息传输或交换。在不同的过程中&#xff0c;双方可以访问哪些媒体&#xff1f;进程的用户空间相互独立。一般来说&#xff0c;他们不能互相接触。唯一的例外是共享内存区域。此外&#xff0c;系统空间是一个“公共场所”&#xff0c;所…

【OFDM系列8】对知乎“正交频分复用(OFDM)原理灵魂9问”的理解与通俗易懂回答(慎入,含大量重要公式详细推导的万字长文)

前段时间,在知乎上看到一篇文章: 正交频分复用(OFDM)原理 文中博主提出了关于OFDM的九个问题,看了之后感觉这些问题的确深入OFDM本质,仔细思考可以很好地加深对OFDM这种较为复杂的调制方式的理解,下面谈一谈本人粗浅的一些理解和粗浅的认识,如有不准确或不恰当之处,欢…

电脑里一辈子都不想删的神仙软件

&#x1f4c2;目录&#x1f388;前言一、浏览器&#xff1a;Chrome新版Edge二、motrix-全能下载器三、压缩软件&#xff1a;WinRAR四、Quicker(更快)五、视频播放器&#xff1a;Potplayer六、listary七、清理工具&#xff1a;Dism八、199it九、PPT排版&#xff1a;iSlide十、 S…

jQuery 动画

文章目录jQuery 动画概述显示和隐藏动画show() 和 hide()toggle()淡入和淡出动画fadeIn() 和 fadeOut()fadeToggle()fadeTo()滑上和滑下动画slideUp()和slideDown()slideToggle()自定义动画简单动画jquery.color.js累积动画回调函数队列动画取消动画延迟动画jQuery 动画 概述 …

程序设计中的耦合类型

一个庞大的系统中&#xff0c;分布着许多子系统&#xff0c;子系统中包含许多包&#xff0c;包中又有数不清的类。自类往上&#xff0c;有数不清的调用关系&#xff0c;一旦两个或多个对象&#xff08;包括类、包、模块、系统等&#xff09;之间发生关系&#xff08;这里我们可…