一文详解Spring StateMachine的概念及应用

这篇具有很好参考价值的文章主要介绍了一文详解Spring StateMachine的概念及应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Spring StateMachine概念及应用

Spring StateMachine是Spring官方提供的一个框架,供应用程序开发人员在Spring应用程序中使用状态机。支持状态的嵌套(substate)、状态的并行(parallel,fork,join)、子状态机等等。状态机可以帮助开发者简化状态控制的开发过程,使状态机结构更加层次化。

Spring StateMachine概念介绍

Spring StateMachine项目模块
一文详解Spring StateMachine的概念及应用
官网地址:https://projects.spring.io/spring-statemachine/

StateMachine关键概念:

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

  • 状态(state)是对象生命周期中满足某种条件、执行某种活动或等待某种事件的条件或情况。

  • 事件(event)在状态机的上下文中,事件是可以触发状态转换的过程。

  • 监视条件(guard condition)在转换的触发事件发生后被评估。只要监视条件不重叠,就可以从相同的源状态中和相同的事件触发器中进行多个转换。在事件发生时,一个保护条件只为转换评估一次。可以用布尔表达式来进行控制。

  • 转换(transition)是两种状态之间的关系,表示处于第一种状态的对象将执行某些操作,并在指定的事件发生且满足指定的条件时进入第二种状态。活动是状态机中正在进行的非原子执行。

  • 动作(action)是一种可执行的原子计算,它导致模型状态的改变或值的返回(一般发生在子状态或者选择状态之间的转换中)。

  • 状态流程配置(StateConfig)是状态机的核心,状态机中状态流转以及事件的触发都是基于状态的配置。Spring StateMachine除去支持简单的状态配置外还支持choice、join、fork、history等状态类型的配置,Spring StateMachine涵盖几乎所有状态机的的状态类型,我们可以充分利用这点特性来完成业务需求。

  • 转换、事件触发器(EventConfig)是定义状态A流转到到状态B会触发的事件,在Spring StateMachine中可以使用OnTransition注解来进行转换的监听或在定义状态流转时进行定义。

StateMachine要素

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

  • 现态:指当前所处的状态。

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

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

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

StateMachine使用场景

  • 程序的某些结构或组成部分可以看作为一个流程的不同状态

  • 希望将程序中复杂的逻辑拆分为较小的可管理的任务

  • 需要解决程序中的某些并发问题,如程序的异步执行

  • 需要循环遍历if-else结构,并对其做进一步的异常处理

状态改变流程

初始化状态 >> 触发事件 >> 消息通知 >> 状态改变

对应Spring StateMachine的核心步骤为:

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

Spring StateMachine的使用

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
	<groupId>org.springframework.statemachine</groupId>
	<artifactId>spring-statemachine-core</artifactId>    
    <version>${spring.statemachine.version}</version>
</dependency>

实例说明

在电商平台中,一个订单会有多种状态:已下单、待支付、已支付、待发货、待收货、已完成等。每一种状态都和变化前的状态以及执行的操作有关。比如当用户点击下单后会生成一个“待支付”的订单;当用户支付完成后这个订单状态就转换为了“待发货”。

前置态+操作 => 后置态

if(前置态1)
{
操作
修改成后置态1
...
}else if(前置态2)
....

一文详解Spring StateMachine的概念及应用

定义状态枚举和事件枚举

public enum States {
        UNPAID,                 // 待支付
        WAITING_FOR_DELIVER,    // 待发货
        WAITING_FOR_RECEIVE,    // 待收货
        DONE                    // 结束
}
public enum Events {
        PAY,        // 支付
        DELIVER,    // 发货
        RECEIVE     // 收货
}

简单状态机实现

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

@Configuration
@EnableStateMachine            //启用状态机
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    //配置初始状态
    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states
                .withStates()
                .initial(States.UNPAID)
                .states(EnumSet.allOf(States.class));
    }

    //配置状态转换的事件关系(多个)
    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States.UNPAID).target(States.WAITING_FOR_DELIVER).event(Events.PAY)
                .and()
                .withExternal()
                .source(States.WAITING_FOR_DELIVER).target(States.WAITING_FOR_RECEIVE)
                .event(Events.DELIVER)
                .and()
                .withExternal()
                .source(States.WAITING_FOR_RECEIVE).target(States.DONE).event(Events.RECEIVE);
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<States, Events> config)
            throws Exception {
        config
                .withConfiguration()
                .listener(listener());
    }

    @Bean
    public StateMachineListener<States, Events> listener() {
        return new StateMachineListenerAdapter<States, Events>() {

            @Override
            public void transition(Transition<States, Events> transition) {
                if(transition.getTarget().getId() == States.UNPAID) {
                    logger.info("订单创建,待支付");
                    return;
                }

                if(transition.getSource().getId() == States.UNPAID
                        && transition.getTarget().getId() == States.WAITING_FOR_DELIVER) {
                    logger.info("用户完成支付,待发货");
                    return;
                }

                if(transition.getSource().getId() == States.WAITING_FOR_DELIVER
                        && transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) {
                    logger.info("订单已发货,待收货");
                    return;
                }

                if(transition.getSource().getId() == States.WAITING_FOR_RECEIVE
                        && transition.getTarget().getId() == States.DONE) {
                    logger.info("用户已收货,订单完成");
                    return;
                }
            }
        };
    }
}

测试类

使用CommandLineRunner接口,在测试类的run方法中启动状态机、发送不同的事件,通过日志验证状态机的流转过程。

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.statemachine.StateMachine;

import javax.annotation.Resource;

@SpringBootApplication
public class Application implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Override
    public void run(String... args) throws Exception {
        createStateMachine();
    }

    @Resource
    StateMachine<OrderStates, OrderEvents> stateMachine;
    public void createStateMachine(){
        stateMachine.start();
        stateMachine.sendEvent(OrderEvents.PAY);
        stateMachine.sendEvent(OrderEvents.DELIVER);
        stateMachine.sendEvent(OrderEvents.RECEIVE);
    }
}

注解监听器

由于上面的实现只是做了一些输出,而在实际业务场景中会有更为复杂的逻辑,因此还有一种方法是将监听器放到独立的类中定义,并通过注入的方式加载进来。

对于状态监听器,Spring StateMachine还提供了优雅的注解配置实现方式,所有在StateMachineListener接口中定义的事件都能通过注解的方式来进行配置实现。因此,在下面的示例中,我们将监听器放到独立的类中定义,并且使用到了@OnTransition注解配置。它省去了原来事件监听器方法中各种if的判断,从而使代码显得更为简洁,具有更好的可读性。

@Component
@WithStateMachine                       //绑定待监听的状态机
public class OrderEventConfig {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @OnTransition(target = "UNPAID")
    public void create(){
        logger.info("订单创建,待支付!");
    }

    @OnTransition(source = "UNPAID",target = "WAITING_FOR_DELIVER")
    public void pay(){
        logger.info("用户完成支付,待发货!");
    }

    @OnTransition(source = "WAITING_FOR_DELIVER",target = "WAITING_FOR_RECEIVE")
    public void deliver(){
        logger.info("订单已发货,待收货!");
    }

    @OnTransition(source = "WAITING_FOR_RECEIVE",target = "DONE")
    public void receive(){
        logger.info("用户已收货,订单完成!");
    }
}

多个状态机共存

在实际项目中一般都会有多个状态机并发执行,比如订单,同一时刻会有不止一个订单在运行,而每个订单都有自己的订单状态机流程。但是在上面的例子中,当执行到某一个状态时,再次刷新页面,不会有任何日志出现。也就是说,当一个状态流程执行到某个状态,再次执行这个状态,是不会有任何输出的,因为状态机的机制是只有在状态切换的时候才会触发事件(event)。因此如果想要实现多个状态机的并行执行,就需要用到builder。

OrderStateMachineBuilder.java

@Component
public class OrderStateMachineBuilder {
    private final static String MACHINEID = "orderStateMachine";
    public StateMachine<OrderStates, OrderEvents> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder();
        Logger logger = LoggerFactory.getLogger(getClass());
        logger.info("构建订单状态机");

        builder.configureConfiguration()
                .withConfiguration()
                .machineId(MACHINEID)
                .beanFactory(beanFactory);

        builder.configureStates()
                .withStates()
                .initial(OrderStates.UNPAID)
                .states(EnumSet.allOf(OrderStates.class));

        builder.configureTransitions()
                .withExternal()
                .source(OrderStates.UNPAID).target(OrderStates.WAITING_FOR_DELIVER)
                .event(OrderEvents.PAY)
                .and()
                .withExternal()
                .source(OrderStates.WAITING_FOR_DELIVER).target(OrderStates.WAITING_FOR_RECEIVE)
                .event(OrderEvents.DELIVER)
                .and()
                .withExternal()
                .source(OrderStates.WAITING_FOR_RECEIVE).target(OrderStates.DONE)
                .event(OrderEvents.RECEIVE);

        return builder.build();
    }
}

其中MACHINEID指向EventConfig,是状态机的配置类和事件实现类的关联。为了能调用到EventConfig,需要在EventConfig中注明状态机的ID。这个id对应的就是OrderStateMachineBuilder 里面的MACHINEID,被builder写到.machineId(MACHINEID)里面。

@Component
@WithStateMachine(id = "orderStateMachine")               //绑定待监听的状态机
public class OrderEventConfig {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @OnTransition(target = "UNPAID")
    public void create(){
        logger.info("订单创建,待支付!");
    }
    ...
}

调用状态机。在调用状态机时候,现在每一次调用都会新建一个状态机并发运行。

@Autowired
private OrderStateMachineBuilder orderStateMachineBuilder;
StateMachine<States,Events> stateMachine = orderStateMachineBuilder.build(beanFactory);
stateMachine.start();
stateMachine.sendEvent(Events.PAY);
stateMachine.sendEvent(Events.DELIVER);
stateMachine.sendEvent(Events.RECEIVE);

多种状态机并存

在实际需求中,服务不同的需求所以往往需要多个状态机,因此一个程序可能有不同种类的状态机。在实际操作中,我们只需用MACHINEID来标识不同的状态机流程就可以在一个程序内创建并使用不同的状态机了。

为此,我们再创建一个新的状态机流程,表单状态机,其状态转换图如下:
一文详解Spring StateMachine的概念及应用

同样为其定义状态枚举和事件枚举

public enum FormStates {
    BLANK_FORM, // 空白表单
    FULL_FORM, // 已填写表单
    CONFIRM_FORM, // 已校验表单
    SUCCESS_FORM // 已提交表单
}
public enum FormEvents {
        WRITE, // 填写
        CONFIRM, // 校验
        SUBMIT // 提交
}

分别创建订单状态机构建器和表单状态构建器。

private final static String MACHINEID = "orderStateMachine";
    public StateMachine<OrderStates, OrderEvents> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder();
        Logger logger = LoggerFactory.getLogger(getClass());
        logger.info("构建订单状态机");

        builder.configureConfiguration()
                .withConfiguration()
                .machineId(MACHINEID)
                .beanFactory(beanFactory);

...
    
private final static String MACHINEID = "formStateMachine";
    public StateMachine<FormStates, FormEvents> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<FormStates, FormEvents> builder = StateMachineBuilder.builder();
        Logger logger = LoggerFactory.getLogger(getClass());
        logger.info("构建表单状态机");

        builder.configureConfiguration()
                .withConfiguration()
                .machineId(MACHINEID)
                .beanFactory(beanFactory);
...

各自声明对应的EventConfig

@WithStateMachine(id = "orderStateMachine")       
public class OrderEventConfig {
...
}
@WithStateMachine(id = "formStateMachine")
public class FormEventConfig {
...
}

在测试类中使用不同的builder就能同时引用不同的状态机,两种状态机就可以互不干扰的各自运行了。文章来源地址https://www.toymoban.com/news/detail-481339.html

@Autowired
private OrderStateMachineBuilder orderStateMachineBuilder;
@Autowired
private FormStateMachineBuilder formStateMachineBuilder;
StateMachine<OrderStates, OrderEvents> orderStateMachine = orderStateMachineBuilder.build(beanFactory);
...
StateMachine<FormStates, FormEvents> formStateMachine = formStateMachineBuilder.build(beanFactory);
...

到了这里,关于一文详解Spring StateMachine的概念及应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Spring Statemachine应用实践

     前言  在日常开发中经常遇到运营审核经销商活动、任务等等类似业务需求,大部分需求中状态稳定且单一无需使用状态机,但是也会出现大量的if...else前置状态代码,也是不够那么的“优雅”。随着业务的发展、需求迭代,每一次的业务代码改动都需要维护使用到状态的

    2024年02月04日
    浏览(35)
  • 数据结构入门(C语言版)二叉树的顺序结构及堆的概念及结构实现应用

    普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用 顺序结构的数组来存储 ,需要注意的是 这里的堆和操作系统虚拟进程地址空间中的堆是两回事 ,一个是 数据结构 ,一

    2023年04月19日
    浏览(53)
  • VRRP的概念及原理

    VRRP(虚拟路由器冗余协议) VRRP是一种选择协议,它可以把一个虚拟路由器的责任动态分配到局域网上的 VRRP 路由器中的一台。控制虚拟路由器 IP 地址的 VRRP 路由器称为主路由器,它负责转发数据包到这些虚拟 IP 地址。一旦主路由器不可用,这种选择过程就提供了动态的故

    2024年02月08日
    浏览(40)
  • VRRP的概念及基本配置

    VRRP可以让多个网关能够协同工作但又不会互相冲突,解决了网关单点故障的问题,并且可以实现网关的备份。 VRRP能够在不改变组网的情况下,将多台路由器虚拟成一个虚拟路由器,通过配置虚拟路由器的IP地址为默认网关,实现网关的备份。 协议版本:VRRPv2(常用)和VRR

    2024年02月05日
    浏览(36)
  • 复数的概念及运算(复变函数)

    1. 复数 z= Re(z)+Im(z) i =x+yi (虚数单位 i,   ) 2.复数的代数运算(加减乘除) 3.共轭复数的性质(加减乘除) (1)                     (2)                            4.复数的几何表示 复平面 z=x+iyP(x,y) 复数的模  ,复数的辐角 Argz,辐角主值argz  复数的幅角:以x轴

    2024年02月03日
    浏览(38)
  • git 中分支的概念及使用

    分支模型是 Git 中的 “必杀技特性”, Git 处理分支的方式非常轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 理解和精通这一特性,我们便会意识到 Git 是如此的强大而又独特,并且从此真正改变我们的开发方式。 在版本控制

    2024年01月17日
    浏览(45)
  • 【Linux】进程的概念及操作进程

    背景: 我们通过冯诺依曼体系结构知道,程序需要运行,需要将其从磁盘中传入内存,在传入CPU,由CPU将其运行起来。 了解了这个背景,我们就能对 进程 得出下面的理解: 进程 就是被加载到内存中的程序,或者被运行起来的程序就叫做 进程 。 这也是很多教材上的说法,

    2024年02月03日
    浏览(37)
  • 完全二叉树——堆的概念及实现

    堆(heap):是堆内存的简称,堆是动态分配内存,内存大小不固定,也不会自动释放,堆——数据结构是一种无序的树状结构,同时它还满足key-value键值对的存储方式。 如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维

    2024年02月07日
    浏览(37)
  • vuex中mapActions的概念及用法

    先看一下官方文档对mapActions的描述: 简单来说 mapActions 就是 将组件中的函数映射为对应的action 。 一般情况下我们会在组件中使用 this.$store.dispatch() 来触发 action ,想要调用多少个 action 就需要调用多少次 dispatch() ,而使用 mapActions 的话只需要 往 mapActions 中传入与 action 同名

    2024年02月13日
    浏览(40)
  • 【数据结构】二叉树的概念及结构

    🚀write in front🚀 📜所属专栏: 初阶数据结构 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!! 关注我,关注我,关注我 , 你们将会看到更多的优质内容!! 树是一种 非线性的数据结构

    2023年04月23日
    浏览(44)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包