0


以电商下单场景为例,玩一玩有限状态机的流转

🍊 Java学习:Java从入门到精通总结

🍊 Spring系列推荐:Spring源码解析

🍊 绝对不一样的职场干货:大厂最佳实践经验指南

📆 最近更新:2022年4月14日

🍊 个人简介:通信工程本硕💪、Java程序员🌕。我的故事充满机遇、挑战与翻盘,欢迎关注作者来共饮一杯鸡汤

文章目录

淘宝下单场景

现代数字社会给人们的生活带来了极大的便利,只需要在手机上点一点就可以购买到自己想要的商品,说到淘宝,用户就必然离不开下单这个功能。

以一个简化版的下单场景为例,一个订单在一开始创建之初是没有任何状态的,当用户点击下单按钮之后订单状态就会变成待付款,用户支付完成之后订单的状态会变成待收货状态,快递签收之后订单完成,如下图所示:

在这里插入图片描述
上面的订单流转图序是一个固定的流程,通常被称为有限状态机,也就是本文要分享给各位的主角。

实际上,在淘宝里,订单的状态非常多,不像上图中那样简单

有限状态机状态间流转要素

要实现状态与状态之间的流转,必须要具备以下几点要素,如下图所示:

在这里插入图片描述
1. 当前状态

状态流转的起始状态,如上图中的新建状态

2. 触发事件

引发状态与状态之间流转的事件,如上图中的创建订单这个动作

3. 响应函数

触发事件到下一个状态之间的规则

4. 目标状态

状态流转的终止状态,如上图中的待付款状态

状态机实现方式盘点

实现有限状态机可以通过以下我整理的其他方式,随着技术的发展,一定还有很多其他的思路,欢迎评论区补充~

1. MQ驱动

订单状态的流转可以通过MQ发布一个事件,消费者根据业务条件把订单状态进行流转,可以根据不同的事件发送到不同的Topic,使得业务逻辑更清晰

2. Job驱动

每隔一段时间启动一下job,根据特定的状态从数据库中拿对应的订单记录,然后判断订单是否有条件到达下一个状态

3. 轻量级状态机

Spring里也有这样的组件,Spring statemachine

上面三种方式非常轻量级,落地容易,如果不涉及非常多的状态流转或复杂的判断条件,就推荐使用这些方式

4. 规则引擎

这种方式比较重量级,通常是大厂才使用的玩法。业务团队可以在规则引擎里自行配置规则,不需要技术团队做发布和代码变更

基于Spring statemachine的轻量级状态机实现

经过上面对状态机的简单介绍,大家应该能建立起一个初始的印象,接下来就用一个实际案例演示一下如何编码吧~~


1. 创建maven项目,pom:

在pom文件里引入状态机

statemachine

,SpringBoot的启动类

spring-boot-starter

,测试类

spring-boot-starter-test

以及开发中常用的工具

lombok
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>spring-statemachine-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-core</artifactId><version>2.1.3.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies></project>

2. 创建订单状态枚举类:

该枚举类用于表示上面状态机流转图里的每一个订单状态,很简单

publicenumOrderState{/*
    创建订单
     */
    CREATED,/*
    等待付款
     */
    PENDING_PAYMENT,/*
    等待配送
     */
    PENDING_DELIVERY,/*
    订单完结
     */
    ORDER_COMPLETE
}

3. 创建事件枚举类:

该枚举类用于表示上面状态机流转图里的每一个触发流转的动作,很简单

publicenumOrderEvent{/*
    下单
     */
    PLACE_ORDER,/*
    付款完成
     */
    PAID,/*
    配送成功
     */
    DELIVERED
}

4. 创建事件监听器:

  • 使用@WithStateMachine注解开启状态机功能
  • @OnTransition(target = "PENDING_PAYMENT")注解的方法表示该操作会使状态流转至PENDING_PAYMENT
  • 定义pendingPayment方法表示用户创建订单操作
  • 定义pendingDelivery方法表示用户付款操作
  • 定义complete方法表示用户签收操作,订单状态变为完结
importcom.wjw.architect.biz.OrderState;importlombok.Data;importlombok.extern.slf4j.Slf4j;importorg.springframework.messaging.Message;importorg.springframework.statemachine.annotation.OnTransition;importorg.springframework.statemachine.annotation.WithStateMachine;@WithStateMachine@Slf4j@DatapublicclassOrderListener{privateString orderStatus =OrderState.CREATED.name();/**
     * 注解中传入的是目标状态
     *
     * @param message 可以从message中拿出一些属性
     */@OnTransition(target ="PENDING_PAYMENT")publicvoidpendingPayment(Message message){
        log.info("订单创建,等待付款, status={} header={}",OrderState.PENDING_PAYMENT.name(),
                message.getHeaders().get("orderId"));// TODO 模拟业务流程setOrderStatus(OrderState.PENDING_PAYMENT.name());}@OnTransition(target ="PENDING_DELIVERY")publicvoidpendingDelivery(){
        log.info("订单已付款,等待发货, status={} ",OrderState.PENDING_DELIVERY.name());// TODO 模拟业务流程setOrderStatus(OrderState.PENDING_DELIVERY.name());}@OnTransition(target ="ORDER_COMPLETE")publicvoidcomplete(){
        log.info("订单完成, status={}",OrderState.ORDER_COMPLETE.name());// TODO 模拟业务流程setOrderStatus(OrderState.ORDER_COMPLETE.name());}}

5. 创建配置类:
定义状态机的初始状态和状态流转的规则:

  • @EnableStateMachine表示开启状态机
  • 该类继承自EnumStateMachineConfigurerAdapter<OrderState, OrderEvent>并重写configure方法指定状态机的初始状态和状态机一共有哪些状态,以及状态之间是如何流转的
importcom.wjw.architect.biz.OrderEvent;importcom.wjw.architect.biz.OrderState;importorg.springframework.context.annotation.Configuration;importorg.springframework.statemachine.config.EnableStateMachine;importorg.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;importorg.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;importorg.springframework.statemachine.config.builders.StateMachineStateConfigurer;importorg.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;importjava.util.EnumSet;@Configuration@EnableStateMachinepublicclassOrderStateMachineConfigextendsEnumStateMachineConfigurerAdapter<OrderState,OrderEvent>{@Overridepublicvoidconfigure(StateMachineStateConfigurer<OrderState,OrderEvent> states)throwsException{
        states.withStates().initial(OrderState.CREATED)// 指定初始化状态.states(EnumSet.allOf(OrderState.class));}/**
     * 配置状态机如何做流转
     *
     * @param transitions
     * @throws Exception
     */@Overridepublicvoidconfigure(StateMachineTransitionConfigurer<OrderState,OrderEvent> transitions)throwsException{
        transitions
                .withExternal()// 配置两个不同状态之间的流转.source(OrderState.CREATED)// 创建订单.target(OrderState.PENDING_PAYMENT)// 待付款.event(OrderEvent.PLACE_ORDER)// 下单.and().withExternal().source(OrderState.PENDING_PAYMENT).target(OrderState.PENDING_DELIVERY).event(OrderEvent.PAID).and().withExternal().source(OrderState.PENDING_DELIVERY).target(OrderState.ORDER_COMPLETE).event(OrderEvent.DELIVERED);}}

6. 创建模拟发送一些事件的类:

手动触发一些事件

importcom.wjw.architect.biz.OrderEvent;importcom.wjw.architect.biz.OrderState;importorg.springframework.boot.CommandLineRunner;importorg.springframework.messaging.Message;importorg.springframework.messaging.support.MessageBuilder;importorg.springframework.statemachine.StateMachine;importorg.w3c.dom.events.Event;importjavax.annotation.Resource;publicclassMyRunnerimplementsCommandLineRunner{@ResourceStateMachine<OrderState,OrderEvent> stateMachine;@Overridepublicvoidrun(String... args)throwsException{
        stateMachine.start();Message message =MessageBuilder.withPayload(OrderEvent.PLACE_ORDER)// 下单事件.setHeader("orderId","998")// 消息头里包含订单id.build();// 发送几个事件
        stateMachine.sendEvent(message);
        stateMachine.sendEvent(OrderEvent.PAID);// 已付款
        stateMachine.sendEvent(OrderEvent.DELIVERED);// 已发货}}

7. 创建启动类:

SpringBoot标准的启动类

importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.Bean;@SpringBootApplicationpublicclassApplicationStarter{@BeanpublicMyRunnerchickenRun(){returnnewMyRunner();}publicstaticvoidmain(String[] arg){SpringApplication.run(ApplicationStarter.class, arg);}}

8. 创建测试类把SpringBoot给跑起来:

@SpringBootTestpublicclassStateMachineTest{@TestpublicvoidrunForrest(){}}

运行结果:

MyRunner将会发送三个事件,这三个事件会触发Listener里三次订单状态的转换
请添加图片描述

此外,Spring statemachine还有一些高级功能:

  • Guard:状态准入、判断当前业务是否可以进入下个状态
  • Action:状态转移之后执行的业务逻辑
标签: java

本文转载自: https://blog.csdn.net/HNU_Csee_wjw/article/details/124135600
版权归原作者 小王曾是少年 所有, 如有侵权,请联系我们删除。

“以电商下单场景为例,玩一玩有限状态机的流转”的评论:

还没有评论