spring状态机

1、概述

        Spring State Machine 是一个用于处理状态机逻辑的框架,它提供了一种简洁的方法来定义状

态、转换以及在状态变更时触发的动作。

概念

  • 状态  State  :一个状态机至少要包含两个状态。例如自动门的例子,有 open 和 closed 两个状态。

  • 事件  Event   :事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。

  • 动作  Action   :事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action一般就对应一个函数。

  • 转换  Transition  :也就是从一个状态变化为另一个状态。例如“开门过程”就是一个转换。

  • 守卫(Guard) :一种条件逻辑,用于决定是否可以进行某个状态转换。守卫可以基于应用程序的当前状态或其他条件来确定转换是否应该发生。

状态机

        有限状态机(Finite-state machine,FSM),又称有限状态自动机,简称状态机,是表示有限

个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM是一种算法思想,简单而言,有

限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组

成。其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事

件。

地址

官方地址:

Spring Statemachine

文档地址

Spring Statemachine - Reference Documentation

github地址

https://github.com/spring-projects/spring-statemachine

2、状态机图

        做需求时,需要了解以下六种元素:起始、终止、现态、次态(目标状态)、动作、条件,

我们就可以完成一个状态机图了,以订单为例:通过支付事件,订单状态从待支付状态转换为待发货状态。

  1. 现态:是指当前所处的状态。待支付。
  2. 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。支付事件。
  3. 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。状态转换为待发货。
  4. 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。待发货。

注意事项:

  1. 避免把某个“程序动作”当作是一种“状态”来处理。那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
  2. 状态划分时漏掉一些状态,导致跳转逻辑不完整。所以在设计状态机时,我们需要反复的查看设计的状态图或者状态表,最终达到一种牢不可破的设计方案。

3、代码

3.1 依赖

 <!--spring 状态机start-->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>${spring-statemachine.version}</version>
</dependency>
<!-- redis持久化状态机 -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-data-redis</artifactId>
    <version>${spring-statemachine.version}</version>
</dependency>
<!--spring 状态机end-->
<!--redis start-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis end-->

3.2 定义状态和事件

package com.ybw.constant;


/**
 * 状态
 *
 * @author weixiansheng
 * @version V1.0
 * @className RegStatusEnum
 * @date 2023/12/26
 **/
public enum RegStatusEnum {
    // 未连接
    UNCONNECTED,
    // 已连接
    CONNECTED,
    // 注册中
    REGISTERING,
    // 已注册
    REGISTERED;
}
package com.ybw.constant;


/**
 * 事件
 *
 * @author weixiansheng
 * @version V1.0
 * @className RegEventEnum
 * @date 2023/12/26
 **/
public enum RegEventEnum {
    // 连接
    CONNECT,
    // 注册
    REGISTER,
    // 注册成功
    REGISTER_SUCCESS,
    // 注册失败
    REGISTER_FAILED,
    // 注销
    UN_REGISTER;
}

3.3 配置状态机

创建一个配置类来配置状态机。在这个配置中,我们定义状态转换逻辑。

package com.ybw.config;

import com.ybw.constant.RegEventEnum;
import com.ybw.constant.RegStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

import java.util.EnumSet;

/**
 * @author weixiansheng
 * @version V1.0
 * @className StateMachineConfig
 * @date 2023/12/25
 **/
@Configuration
@EnableStateMachine
@Slf4j
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {

    /**
     * 配置状态
     *
     * @param states
     * @methodName: configure
     * @return: void
     * @author: weixiansheng
     * @date: 2023/12/25
     **/
    @Override
    public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
        states.withStates()
                // 定义初始状态
                .initial(RegStatusEnum.UNCONNECTED)
                // 定义状态机状态
                .states(EnumSet.allOf(RegStatusEnum.class));
    }

    /**
     * 配置状态转换事件关系
     *
     * @param transitions
     * @methodName: configure
     * @return: void
     * @author: weixiansheng
     * @date: 2023/12/25
     **/
    @Override
    public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions) throws Exception {
        transitions
                // 1.连接事件
                // 未连接 -> 已连接
                .withExternal().source(RegStatusEnum.UNCONNECTED).target(RegStatusEnum.CONNECTED).event(RegEventEnum.CONNECT).and()

                // 2.注册事件
                // 已连接 -> 注册中
                .withExternal().source(RegStatusEnum.CONNECTED).target(RegStatusEnum.REGISTERING).event(RegEventEnum.REGISTER).and()

                // 3.注册成功事件
                // 注册中 -> 已注册
                .withExternal().source(RegStatusEnum.REGISTERING).target(RegStatusEnum.REGISTERED).event(RegEventEnum.REGISTER_SUCCESS).and()

                // 5.注销事件
                // 已连接 -> 未连接
                .withExternal().source(RegStatusEnum.CONNECTED).target(RegStatusEnum.UNCONNECTED).event(RegEventEnum.UN_REGISTER).and()
                // 注册中 -> 未连接
                .withExternal().source(RegStatusEnum.REGISTERING).target(RegStatusEnum.UNCONNECTED).event(RegEventEnum.UN_REGISTER).and()
                // 已注册 -> 未连接
                .withExternal().source(RegStatusEnum.REGISTERED).target(RegStatusEnum.UNCONNECTED).event(RegEventEnum.UN_REGISTER);
    }
}

3.4 状态机的转换事件配置

package com.ybw.config;

import com.alibaba.fastjson2.JSON;
import com.ybw.constant.RegEventEnum;
import com.ybw.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;

/**
 * 状态机的转换事件配置
 *
 * @author weixiansheng
 * @version V1.0
 * @className StateMachineEventConfig
 * @date 2023/12/26
 **/
@WithStateMachine
@Slf4j
public class StateMachineEventConfig {
    /**
     * 连接事件
     *
     * @param message
     * @methodName: connect
     * @return: void
     * @author: weixiansheng
     * @date: 2023/12/26
     **/
    @OnTransition(source = "UNCONNECTED", target = "CONNECTED")
    public void connect(Message<RegEventEnum> message) {
        Order order = (Order) message.getHeaders().get("order");
        log.info("///");
        log.info("连接事件, 未连接 -> 已连接,order:{}", JSON.toJSONString(order));
        log.info("///");
    }

    /**
     * 注册事件
     *
     * @param message
     * @methodName: register
     * @return: void
     * @author: weixiansheng
     * @date: 2023/12/26
     **/
    @OnTransition(source = "CONNECTED", target = "REGISTERING")
    public void register(Message<RegEventEnum> message) {
        log.info("///");
        log.info("注册事件, 已连接 -> 注册中");
        log.info("///");
    }

    /**
     * 注册成功事件
     *
     * @param message
     * @methodName: registerSuccess
     * @return: void
     * @author: weixiansheng
     * @date: 2023/12/26
     **/
    @OnTransition(source = "REGISTERING", target = "REGISTERED")
    public void registerSuccess(Message<RegEventEnum> message) {
        log.info("///");
        log.info("注册成功事件, 注册中 -> 已注册");
        log.info("///");
    }

    /**
     * 注销事件
     *
     * @param message
     * @methodName: unRegister
     * @return: void
     * @author: weixiansheng
     * @date: 2023/12/26
     **/
    @OnTransition(source = "REGISTERED", target = "UNCONNECTED")
    public void unRegister(Message<RegEventEnum> message) {
        log.info("///");
        log.info("注销事件, 已注册 -> 未连接");
        log.info("///");
    }
}

3.6 Redis持久化

持久化到redis中,在分布式系统中使用。

package com.ybw.config;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.statemachine.data.redis.RedisStateMachineContextRepository;
import org.springframework.statemachine.data.redis.RedisStateMachinePersister;
import org.springframework.statemachine.persist.RepositoryStateMachinePersist;


/**
 * 持久化
 *
 * @author weixiansheng
 * @version V1.0
 * @className Persist
 * @date 2023/12/26
 **/
@Configuration
@Slf4j
public class Persist<E, S> {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 持久化到redis中,在分布式系统中使用
     *
     * @methodName: getRedisPersister
     * @return: org.springframework.statemachine.data.redis.RedisStateMachinePersister<E, S>
     * @author: weixiansheng
     * @date: 2023/12/26
     **/
    @Bean(name = "stateMachineRedisPersister")
    public RedisStateMachinePersister<E, S> getRedisPersister() {
        RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
        RepositoryStateMachinePersist<E, S> p = new RepositoryStateMachinePersist<>(repository);
        return new RedisStateMachinePersister<>(p);
    }
}

3.6 测试

package com.ybw.state;

import com.ybw.constant.RegEventEnum;
import com.ybw.constant.RegStatusEnum;
import com.ybw.entity.Order;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.data.redis.RedisStateMachinePersister;
import org.springframework.statemachine.persist.StateMachinePersister;

/**
 * @author weixiansheng
 * @version V1.0
 * @className StateTest
 * @date 2023/12/26
 **/
@SpringBootTest
@Slf4j
public class StateTest {
    @Resource
    private StateMachine<RegStatusEnum, RegEventEnum> stateMachine;
    @Resource(name = "stateMachineRedisPersister")
    private RedisStateMachinePersister<RegStatusEnum, RegEventEnum> persister;


    /**
     * @MethodName: testState
     * @Description:
     * @Param: []
     * @Return: void
     * @Author: ybwei
     * @Date: 2020/3/26
     **/
    @Test
    public void testState() {
        try {
            Order order = Order.builder()
                    .id(1L)
                    .name("张三")
                    .status(RegStatusEnum.CONNECTED)
                    .build();
            stateMachine.start();
            //尝试恢复状态机状态(read)
            persister.restore(stateMachine, order.getId().toString());
            Message<RegEventEnum> message = MessageBuilder.withPayload(RegEventEnum.CONNECT).setHeader("order", order).build();
            stateMachine.sendEvent(message);
            //持久化状态机状态(write)
            persister.persist(stateMachine, order.getId().toString());
        } catch (Exception e) {
            log.error("testState error:", e);
        }
//        stateMachine.sendEvent(RegEventEnum.REGISTER);
//        stateMachine.sendEvent(RegEventEnum.REGISTER_SUCCESS);
//        stateMachine.sendEvent(RegEventEnum.UN_REGISTER);
//
//        stateMachine.sendEvent(RegEventEnum.CONNECT);
    }
}

 执行结果:

第一次执行

[INFO ] 2023-12-26 17:35:20.563 [main] o.s.s.support.AbstractStateMachine - Got null context, resetting to initial state, clearing extended state and machine id
[INFO ] 2023-12-26 17:35:20.593 [main] c.ybw.config.StateMachineEventConfig - ///
[INFO ] 2023-12-26 17:35:20.688 [main] c.ybw.config.StateMachineEventConfig - 连接事件, 未连接 -> 已连接,order:{"id":1,"name":"张三","status":"CONNECTED"}
[INFO ] 2023-12-26 17:35:20.690 [main] c.ybw.config.StateMachineEventConfig - ///

第二次执行,没有日志打印。

修改代码

将RegEventEnum.CONNECT改为RegEventEnum.REGISTER

@Test
public void testState() {
    try {
        Order order = Order.builder()
                .id(1L)
                .name("张三")
                .status(RegStatusEnum.CONNECTED)
                .build();
        stateMachine.start();
        //尝试恢复状态机状态(read)
        persister.restore(stateMachine, order.getId().toString());
        Message<RegEventEnum> message = MessageBuilder.withPayload(RegEventEnum.REGISTER).setHeader("order", order).build();
        stateMachine.sendEvent(message);
        //持久化状态机状态(write)
        persister.persist(stateMachine, order.getId().toString());
    } catch (Exception e) {
        log.error("testState error:", e);
    }
}

第三次执行,打印日志

[INFO ] 2023-12-26 17:38:31.307 [main] c.ybw.config.StateMachineEventConfig - ///
[INFO ] 2023-12-26 17:38:31.307 [main] c.ybw.config.StateMachineEventConfig - 注册事件, 已连接 -> 注册中
[INFO ] 2023-12-26 17:38:31.307 [main] c.ybw.config.StateMachineEventConfig - ///

 总结:

  • 事件发生后,会执行StateMachineEventConfig相关逻辑。
  • 持久化状态机状态。
  • 相同的事件再次发生,不会执行StateMachineEventConfig相关逻辑。

3.7 源码地址

share: 分享仓库 - Gitee.com

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用芯片SIC8833可开发电子秤方案

SIC8833作为一款高性能的电子秤方案芯片&#xff0c;这款芯片是一个带24bitADC的8位RISC MCU&#xff0c;内置8k16位OTP程序存储器。具体24位双向I/O口的特性&#xff0c;广泛应用于电子衡器和精密测量及控制系统&#xff0c;能满足用户的不同需求和应用场景。 以下是电子秤方案…

盘古信息IMS-MOM制造运营管理系统,构建生产现场管理信息系统的最佳选择

在当今高度竞争的制造行业中&#xff0c;高效的生产管理是企业成功的关键。盘古信息IMS-MOM制造运营管理系统作为一款领先的管理系统其关键特性为制造企业构建生产现场管理信息系统提供了强大的优势。IMS-MOM不仅仅是一个软件系统&#xff0c;更是一种技术和管理手段的结合&…

【SQL经典题目】连续日期判断、同时在线人数、会话划分、间隔日期连续、日期交叉

【1.查询至少连续3天下单的用户】 思路1&#xff08;使用lead&#xff09;&#xff1a; distinct user_id,create_date去重&#xff0c;确保每个用户每天只有一条访问记录lead(create_date,2,‘9999-12-31’) over(partition by user_id order by create_date)根据用户分区&am…

力扣刷题记录(19)LeetCode:279、139

279. 完全平方数 这题和上篇文章的题类似&#xff0c;直接上代码 class Solution { public:int numSquares(int n) {vector<int> dp(n1,INT_MAX);dp[0]0;//j表示背包容量&#xff0c;dp[j]表示和为n的完全平方数的最少数量for(int i0;i*i<n;i){for(int ji*i;j<n;j…

【Java】springboot

文章目录 Spingboot1、起步依赖2、构建springboot工程jar包3、springboot配置文件4、多环境配置5、maven和boot多环境兼容问题6、配置文件分类7、springboot整合mybatis Spingboot springboot用来简化spring的初始搭建以及开发过程。 比方说&#xff0c;创建一个springmvc程序…

AI时代下,如何看待“算法利维坦”?

ChatGPT的浪潮从2022年袭来后&#xff0c;至今热度不减&#xff0c;呈现出蓬勃发展的趋势。AI家居、医疗、教育、金融、公益、农业、艺术…AI真的已经走进了生活的方方面面&#xff0c;我们仿佛已经进入了AI时代&#xff0c;势不可挡。人工智能水平如此之高&#xff0c;不禁感慨…

医疗器械行业为什么要搭建自己的知识付费平台

随着医疗技术的不断进步&#xff0c;医疗器械行业正迅速发展&#xff0c;成为全球范围内的热门产业。医疗器械行业需要不断更新技术、提升产品质量、加强用户培训和推广新产品。在这个过程中&#xff0c;搭建自己的知识付费平台变得越来越重要&#xff0c;本文将深入探讨为什么…

DAY1C++

1、思维导图 2.定义自己的命名空间myspace&#xff0c;并在myspace中定义一个字符串&#xff0c;实现求字符串大小的函数。 #include <iostream>using namespace std;namespace myspace{string s1("call your name");int len_s(string s){return s1.length();…

关于Word中隐藏文本的知识,看这篇就够了

Word允许你隐藏文本&#xff0c;这样你就可以阅读或打印文档&#xff0c;就好像文本不在那里一样。这似乎毫无意义&#xff0c;因为如果你不想让别人阅读&#xff0c;为什么不删除文本呢&#xff1f;但隐藏文本确实有一些有趣的用途。让我们来看看什么是隐藏文本&#xff08;什…

Python(六)—— 自定义模块

15. 自定义模块 15.1 模块的定义与分类 15.1.1 模块的定义 一个函数封装一个功能&#xff0c;当遇到众多函数时&#xff0c;将这些相同的功能封装到一个文件中&#xff0c;那么这个存储着很多常用的功能的py文件&#xff0c;就是模块。模块就是文件&#xff0c;存放一堆常用…

Oracle 19c OCP 082考场真题解析第16题

考试科目&#xff1a;1Z0-082 考试题量&#xff1a;90 通过分数&#xff1a;60% 考试时间&#xff1a;150min 本文为云贝教育郭一军guoyJoe原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载。【云贝教育】Orac…

腾讯云上mysql连接不上

腾讯云服务器默认没开放&#xff0c;3306端口。 1.去腾讯云控制台 2.找到自己的服务器 3选择防火墙 4.添加规则 至此完事了。

Checkpoint 执行机制原理解析

在介绍Checkpoint的执行机制前&#xff0c;我们需要了解一下state的存储&#xff0c;因为state是Checkpoint进行持久化备份的主要角色。Checkpoint作为Flink最基础也是最关键的容错机制&#xff0c;Checkpoint快照机制很好地保证了Flink应用从异常状态恢复后的数据准确性。同时…

搞懂SkyWalking(40张图)

前言 在微服务架构中&#xff0c;一次请求往往涉及到多个模块&#xff0c;多个中间件&#xff0c;多台机器的相互协作才能完成。这一系列调用请求中&#xff0c;有些是串行的&#xff0c;有些是并行的&#xff0c;那么如何确定这个请求背后调用了哪些应用&#xff0c;哪些模块…

Python 高级(三):多线程 threading

大家好&#xff0c;我是水滴~~ 在Python中&#xff0c;threading模块提供了一种简单而强大的方式来进行多线程编程。多线程可以同时执行多个任务&#xff0c;使程序能够更有效地利用计算资源。本教程将介绍threading模块的基本概念、用法和一些常见的多线程编程模式。 文章中…

【JAVA】黑马MybatisPlus 学习笔记【终】【插件功能】

4.插件功能 MybatisPlus提供了很多的插件功能&#xff0c;进一步拓展其功能。目前已有的插件有&#xff1a; PaginationInnerInterceptor&#xff1a;自动分页TenantLineInnerInterceptor&#xff1a;多租户DynamicTableNameInnerInterceptor&#xff1a;动态表名OptimisticL…

Python 新规范 pyproject.toml 完全解析

多谢&#xff1a;thank Python从PEP 518开始引入的使用pyproject.toml管理项目元数据的方案。 该规范目前已经在很多开源项目中得以支持&#xff1a; Django 这个 Python 生态的顶级项目在 5 个月之前开始使用 pyproject.tomlPytest 这个 Python 生态测试框架的领头羊在 4 个…

HarmonyOS4.0系统性深入开发04UIAbility组件详解(下)

UIAbility组件间交互&#xff08;设备内&#xff09; UIAbility是系统调度的最小单元。在设备内的功能模块之间跳转时&#xff0c;会涉及到启动特定的UIAbility&#xff0c;该UIAbility可以是应用内的其他UIAbility&#xff0c;也可以是其他应用的UIAbility&#xff08;例如启…

java练习题之接口interface练习

1&#xff1a;关于接口和抽象类&#xff0c;下列说法正确的是&#xff08;ACD&#xff09; A.抽象类可以有构造方法&#xff0c;接口没有构造方法 B.抽象类可以有属性&#xff0c;接口没有属性 C.抽象类可以有非抽象方法&#xff0c;接口中都是抽象方法 1.8之后 D.抽象类和接…

比亚迪重磅来袭,汽车圈又要大动干戈?

12月15日&#xff0c;我盼望已久的新车————宋L正式登场&#xff01; 作为一直关注比亚迪的车主&#xff0c;这款新SUV一直处于我的观测范围内。终于在前几日&#xff0c;比亚迪宣布它将于12月15日上市&#xff0c;这对我来说无疑是个好消息。当我了解到宋L将推出后驱和四驱…