【微服务】spring状态机模式使用详解

news2024/11/23 15:16:43

一、前言

在很多系统中,通常会涉及到某个业务需要进行各种状态的切换操作,例如在审批流程场景下,某个审批的向下流转需要依赖于上一个状态的结束,再比如电商购物场景中,一个订单的生命周期往往伴随着不同的状态,比如待支付,支付完成,已发货等等,状态的存在,让一个业务的完整流程得以串联,所以状态在真实的场景中具有重要的意义。

二、spring状态机介绍

在开始学习spring状态机之前,有一些概念需要弄清楚,弄清这些概念,才能更好的理解spring状态机。

2.1 什么是状态

在java中,状态(State)是指对象在某一时刻所处的条件或情况,类比于生活中的场景理解,一台机器有运行和停止状态,一个门有关闭和打开状态。状态通常是描述某种事务在某个时刻所处的一种情形。

在Spring状态机中,状态(State)是指对象在某一时刻所处的条件或情况。状态描述了对象的特定属性、行为或情况,是有限状态机(Finite State Machine,FSM)模型中的一个重要概念。

2.2 状态的几个概念

这里特指spring状态机中关于状态的描述。在spring中,状态机通常是有限状态机,即事务实际发生的情形,其状态是可控的,在一定的范围内发生。具体来说:

  • 状态(State):状态是指对象在某一时刻的特定条件或情况。它描述了对象所处的状态,可以是系统中的一个正常状态、初始状态或终止状态。状态是有限状态机的核心概念,用于描述对象的属性和行为。
  • 事件(Event):事件是导致状态转换发生的触发器或信号。当系统接收到特定的事件时,状态机会根据预先定义的规则将对象从当前状态转移到下一个状态。事件可以是外部输入、时间触发、条件满足等引起状态变化的因素。
  • 转移(Transition):转移定义了从一个状态到另一个状态的过程。它描述了状态之间的关系和转换规则,指定了在接收特定事件时应该执行的状态转换操作。转移通常包括源状态、目标状态和触发转移的事件。
  • 行为(Action):行为,也叫动作,是与状态转换相关联的具体动作或逻辑。当状态机执行状态转换时,可以触发与该转换相关的行为来处理额外的逻辑操作。行为可以是方法调用、数据更新、日志记录等对状态变化进行响应的操作。

2.3 什么是状态机

状态机,又称有限状态机,全称:Finite-state machine(FSM)。是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。

2.4 spring 状态机

2.4.1 spring 状态机概述

Spring状态机(Spring State Machine)是Spring框架提供的一个模块,用于帮助开发者轻松地实现状态机功能。Spring状态机模块提供了一个基于有限状态机(Finite State Machine, FSM)模型的框架,使开发者可以定义状态、事件和状态之间的转换,并能够对状态变化进行管理和监控。通过Spring状态机,开发者可以更方便地实现复杂的业务逻辑、工作流程和状态管理。

2.4.2 spring 状态机特点

Spring状态机的特点包括:

  • 灵活性好

    • 开发者可以自定义状态、事件、转换规则以及监听器,以满足各种场景下的需求。

  • 可扩展性强

    • Spring状态机提供了丰富的扩展点和API,开发者可以根据实际需求进行扩展和定制。

  • 易用性高

    • Spring状态机封装了状态机模型的复杂性,提供了简洁的API和注解,使得使用起来更加方便快捷。

  • 集成方便

    • Spring状态机与Spring框架无缝集成,可以与其他Spring组件(如Spring Boot、Spring MVC等)结合使用。

通过使用Spring状态机,开发者可以更好地管理应用程序中的状态,简化状态转换逻辑,提高代码可读性和可维护性。

2.4.3 spring 状态机中的几个状态

在Spring状态机中,状态通常用字符串或枚举类型表示,开发者可以定义不同的状态来描述对象在系统中可能存在的各种情况。状态在状态机中扮演着重要的角色,确定了对象如何响应事件以及转移到下一个状态的规则。

在使用Spring状态机时,可以通过配置来定义状态,并且将这些状态与具体的业务逻辑进行关联。在状态机中,通常包括以下几种类型的状态:

  • Initial State(初始状态):状态机启动时的初始状态,是状态机的起点。只能有一个初始状态。

  • Normal State(普通状态):描述对象在系统中的一种正常状态,可以根据业务需求定义多个普通状态。

  • Final State(终止状态):描述对象完成某种操作或任务后达到的结束状态,表示状态机执行完毕的终点。通常对应于任务完成或异常终止等情况。

定义好状态后,接着需要定义状态之间的转换规则,即指定在接收特定事件时,对象应该从当前状态转移到哪个状态。这样,通过触发事件,状态机会根据定义的规则自动进行状态转换,驱动对象在不同状态下进行行为变化。

2.4.4 spring状态机原理

  • Spring状态机建立在有限状态机(FSM)的概念之上,提供了一种简洁且灵活的方式来定义、管理和执行状态机;

  • 它将状态定义为Java对象,并通过配置来定义状态之间的转换规则;

  • 状态转换通常由外部事件触发,我们可以根据业务逻辑定义不同的事件类型,并与状态转换关联;

  • Spring状态机还提供了状态监听器,用于在状态变化时执行特定的逻辑。同时,状态机的状态可以持久化到数据库或其他存储介质中,以便在系统重启或故障恢复时保持状态的一致性。

2.4.5 spring状态机的几个核心元素

Spring状态机核心主要包括以下三个关键元素:

  • 状态(State):定义了系统可能处于的各个状态。

    • 如订单状态中的待支付、已支付等。

  • 转换(Transition):描述了在何种条件下,当接收到特定事件时,系统可以从一个状态转移到另一个状态。

    • 例如,接收到“支付成功”事件时,订单状态从“待支付”转变为“已支付”。

  • 事件(Event):触发状态转换的动作或者消息,它是引起状态机从当前状态迁移到新状态的原因。

2.5 spring 状态机使用场景

Spring状态机在实际应用中有许多使用场景,下面列举一些常用的应用场景。

工作流管理

Spring状态机可用于实现复杂的工作流控制,如订单处理、审批流程、报销流程等。通过定义不同状态和事件,可以规范流程执行顺序,提高工作效率和可控性。

设备控制

在物联网和嵌入式系统中,设备通常存在不同的工作状态和模式。通过使用Spring状态机,可以实现设备状态管理和控制,如设备启动、停止、故障处理等。

订单生命周期管理

电商系统中,订单状态订单生命周期包含多个状态(待支付、已支付、待发货、已发货等)。利用Spring状态机可以清晰地定义订单状态及状态转换规则,简化订单状态管理。

流程控制

在业务系统中,一些复杂的流程需要根据不同条件或事件进行状态转换。通过Spring状态机,可以将业务流程分解成状态和事件,实现清晰的流程控制和跟踪。

会话管理

在web应用中,用户的会话状态,登录,登出,活跃,超时等,可以借助状态机有效控制,简化会话状态相关的复杂业务逻辑。

游戏开发

在游戏开发中,状态机经常用于描述角色状态、技能释放、战斗流程等。借助Spring状态机框架,开发者可以轻松地管理游戏对象的状态转换和行为逻辑。

自动化测试

在自动化测试中,状态机可以用于描述测试用例的执行流程和状态切换,帮助进行整体测试计划的规划和执行。

总的来说,Spring状态机适用于各种需要状态管理和状态转换的场景,特别是那些涉及复杂流程、状态变化频繁或需要规范化控制的应用领域。通过合理应用状态机模型,可以简化系统设计、提高代码可维护性,并实现更清晰、可控的业务流程和状态管理。希望这些场景示例对您有所启发。

三、与springboot整合使用

接下来演示如何在springboot中使用状态机。工程目录结构如下:

3.1 操作步骤

本例将以一个订单支付的业务为场景进行说明,下面来看具体的操作步骤。

3.1.1 引入依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-starter</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3.1.2 定义订单状态枚举类

该类定义了订单在实际业务中的几种常用的状态,作为后面业务逻辑操作过程中的执行依据。

状态是状态机的核心组成单元,比如这里的一个订单在实际业务中的各种状态,状态代表了系统或对象在某一时刻可能存在的条件或模式。在状态机中,每一个状态都是系统可能处于的一种明确的条件或阶段。每个状态都是独一无二的,且在任何给定时间,系统只能处于其中一个状态。

public enum OrderStatusEnum {

    /**
     * 待提交
     */
    DRAFT,

    /**
     * 待出库
     */
    SUBMITTED,

    /**
     * 已出库
     */
    DELIVERING,

    /**
     * 已签收
     */
    SIGNED,

    /**
     * 已完成
     */
    FINISHED,

    ;
}

3.1.3 定义订单事件流转枚举类

转状态换(状态切换),指状态之间的转变过程,它是状态机模型动态性体现。当一个外部事件,如用户按下按钮、接收到信号、满足特定条件等触发时,状态机会从当前状态转移到另一个状态。在定义转换时,需要指出触发转换的事件(Event),以及事件发生时系统的响应,即从哪个状态(Source State)转到哪个状态(Target State)。在如下的枚举类中,定义了与订单状态相对应的触发事件,比如大家熟悉的订单发货,订单签收等。

public enum OrderStatusOperateEventEnum {

    /**
     * 确认订单,已提交
     */
    CONFIRMED,

    /**
     * 订单发货
     */
    DELIVERY,

    /**
     * 订单签收
     */
    RECEIVED,

    /**
     * 订单完成
     */
    CONFIRMED_FINISH,

    ;
}

3.1.4 订单状态机配置类

状态机配置类,是在使用Spring State Machine或其他状态机框架时的一个重要步骤,该类主要用于定义状态机的核心结构,包括状态(states)、事件(events)、状态之间的转换规则(transitions),以及可能的状态迁移动作和决策逻辑。

在Spring State Machine中,创建状态机配置类通常是通过继承StateMachineConfigurerAdapter类来实现的。这个适配器类提供了几个模板方法,允许开发者重写它们来配置状态机的各种组成部分:

  • 配置状态(configureStates(StateMachineStateConfigurer))

    • 该方法中,开发者定义状态机中所有的状态,包括初始状态(initial state)和结束状态(final/terminal states)。例如,定义状态A、B、C,并指定状态A作为初始状态。

  • 配置转换(configureTransitions(StateMachineTransitionConfigurer))

    • 在这里,开发者描述状态之间的转换规则,也就是当某个事件(event)发生时,状态机如何从一个状态转移到另一个状态。例如,当事件X发生时,状态机从状态A转移到状态B。

  • 配置初始状态(configureInitialState(ConfigurableStateMachineInitializer))

    • 如果需要显式指定状态机启动时的初始状态,可以在该方法中设置。

import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

import java.util.EnumSet;

/**
 * 订单状态机配置
 * 处理订单流程流转,与订单事件的配置
 */
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusOperateEventEnum> {

    /**
     * 设置状态机的状态 ,初始态和结束态
     * StateMachineStateConfigurer 即 状态机状态配置
     * @param states 状态机状态
     * @throws Exception 异常
     */
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusOperateEventEnum> states) throws Exception {
        states.withStates()
                .initial(OrderStatusEnum.DRAFT)
                .end(OrderStatusEnum.FINISHED)
                //囊括了订单的所有状态
                .states(EnumSet.allOf(OrderStatusEnum.class));
    }

    /**
     * 配置状态转换与事件的关系,说明了订单状态的转换与订单事件之间的关系,其中source target event 是一组配合使用
     * @param transitions
     * @throws Exception
     * source 原始的状态
     * target 目标状态
     * event 通过什么事件触发
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusOperateEventEnum> transitions) throws Exception {
        transitions.withExternal()
                .source(OrderStatusEnum.DRAFT).target(OrderStatusEnum.SUBMITTED)   //待提交与待出库状态的扭转
                .event(OrderStatusOperateEventEnum.CONFIRMED)                       //待提交与待出库状态的扭转,当被触发了 CONFIRMED 这个状态的时候,下同
                .and()
                .withExternal().source(OrderStatusEnum.SUBMITTED).target(OrderStatusEnum.DELIVERING)
                .event(OrderStatusOperateEventEnum.DELIVERY)
                .and()
                .withExternal().source(OrderStatusEnum.DELIVERING).target(OrderStatusEnum.SIGNED)
                .event(OrderStatusOperateEventEnum.RECEIVED)
                .and()
                .withExternal().source(OrderStatusEnum.SIGNED).target(OrderStatusEnum.FINISHED)
                .event(OrderStatusOperateEventEnum.CONFIRMED_FINISH);

    }
}

3.1.5 定义状态机监听器

状态机监听器(State Machine Listener)是一种组件,它可以监听并响应状态机在运行过程中的各种事件,例如状态变迁、进入或退出状态、转换被拒绝等。

在Spring Statemachine中,监听器可以通过实现StateMachineListener接口来定义。该接口提供了一系列回调方法,如transitionTriggered、stateEntered、stateExited等,当状态机触发转换、进入新状态或离开旧状态时,这些方法会被调用。同时,我们也可以通过注解实现监听器。注解方式可以在类的方法上直接声明该方法应该在何种状态下被调用,简化监听器的编写和配置。例如@OnTransition,@OnTransitionEnd,@OnTransitionStart等

import com.congge.entity.OrderDO;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;

@Component("orderStatusListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStatusListener {

    /**
     * 待提交到出库事件扭转时,该方法将会被触发
     * @param message
     * @return
     */
    @OnTransition(source = "DRAFT", target = "SUBMITTED")
    public boolean pay(Message<OrderStatusOperateEventEnum> message) {
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.SUBMITTED);
        System.out.println(String.format("出库订单[%s]确认,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }

    /**
     * 待出库到出库的事件扭转
     * @param message
     * @return
     */
    @OnTransition(source = "SUBMITTED", target = "DELIVERING")
    public boolean deliver(Message<OrderStatusOperateEventEnum> message) {
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.DELIVERING);
        System.out.println(String.format("出库订单[%s]发货出库,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }

    /**
     * 出库到签收的事件扭转
     * @param message
     * @return
     */
    @OnTransition(source = "DELIVERING", target = "SIGNED")
    public boolean receive(Message<OrderStatusOperateEventEnum> message){
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.SIGNED);
        System.out.println(String.format("出库订单[%s]签收,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }

    /**
     * 签收到订单完成状态扭转
     * @param message
     * @return
     */
    @OnTransition(source = "SIGNED", target = "FINISHED")
    public boolean finish(Message<OrderStatusOperateEventEnum> message){
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.FINISHED);
        System.out.println(String.format("出库订单[%s]完成,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }
}

3.1.6 配置状态机持久化类

状态机持久化,是指将状态机在某一时刻的状态信息存储到数据库、缓存等介质中,这样的话,即使在系统重启、网络故障或进程终止等情况下,状态机仍能从先前保存的状态继续执行,而不是从初始状态重新开始。

在实际业务场景中,如订单处理、工作流引擎、游戏进度跟踪等,状态机通常用于表示某个实体在其生命周期内的状态变迁。如果没有持久化机制,一旦发生意外情况导致系统宕机或重启,未完成的状态变迁将会丢失,这对于业务连续性和一致性是非常不利的。

状态机持久化通常涉及以下几个方面:

  • 状态记录:记录当前状态机实例处于哪个状态;

  • 上下文数据:除了状态外,可能还需要持久化与状态关联的上下文数据,例如触发状态变迁的事件参数、额外的状态属性等;

  • 历史轨迹:某些复杂场景下可能需要记录状态机的历史变迁轨迹,以便于审计、回溯分析或错误恢复;

  • 并发控制:在多线程或多节点环境下,状态机的持久化还要考虑并发访问和同步的问题;

import com.congge.entity.OrderDO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class OrderPersist {

    /**
     * 持久化配置
     * 在实际使用中,可以配合数据库或者Redis等进行持久化操作
     * @return
     */
    @Bean
    public DefaultStateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister(){
        Map<OrderDO, StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum>> map = new HashMap<>();
        return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO>() {
            @Override
            public void write(StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> context, OrderDO order) throws Exception {
                //持久化操作
                map.put(order, context);
            }

            @Override
            public StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> read(OrderDO order) throws Exception {
                //从库中或者redis中读取order的状态信息
                return map.get(order);
            }

        });
    }
}

3.1.7 发送状态转换类

在上面定义了状态机的监听器,而监听器需要监听到状态流转的事件才会发挥作用,才能监听到某个状态事件之后,完成状态的变更,这就需要一个 发送状态转换的处理方法。

import com.congge.entity.OrderDO;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;

@Component
public class StateEventUtil {

    @Resource
    private StateMachine<OrderStatusEnum, OrderStatusOperateEventEnum> orderStateMachine;

    @Resource
    private StateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister;

    /**
     * 发送状态转换事件
     *  synchronized修饰保证这个方法是线程安全的
     * @param message
     * @return
     */
    public synchronized boolean sendEvent(Message<OrderStatusOperateEventEnum> message) {
        boolean result = false;
        try {
            //启动状态机
            orderStateMachine.start();

            OrderDO order = (OrderDO) message.getHeaders().get("order");
            //尝试恢复状态机状态
            stateMachinePersister.restore(orderStateMachine, order);
            result = orderStateMachine.sendEvent(message);

            //持久化状态机状态
            stateMachinePersister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (Objects.nonNull(message)) {
                OrderDO order = (OrderDO) message.getHeaders().get("order");
                if (Objects.nonNull(order) && Objects.equals(order.getOrderStatusEnum(), OrderStatusEnum.FINISHED)) {
                    orderStateMachine.stop();
                }
            }
        }
        return result;
    }
}

3.1.8 订单业务类

下面是订单核心业务逻辑的实现,包括与订单的各种状态相关的方法

import com.congge.config.OrderStatusEnum;
import com.congge.config.OrderStatusOperateEventEnum;
import com.congge.config.StateEventUtil;
import com.congge.entity.OrderDO;
import com.congge.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class OrderServiceImpl implements OrderService {

    private StateEventUtil stateEventUtil;

    private static final AtomicInteger ID_COUNTER = new AtomicInteger(0);

    private static final Map<Long, OrderDO> ORDER_MAP = new ConcurrentHashMap<>();

    /**
     * 创建订单
     * @param orderDO
     */
    @Override
    public Long createOrder(OrderDO orderDO) {
        long orderId = ID_COUNTER.incrementAndGet();
        orderDO.setOrderId(orderId);
        orderDO.setOrderNo("AHbb1-" + orderId);
        orderDO.setOrderStatusEnum(OrderStatusEnum.DRAFT);
        ORDER_MAP.put(orderId, orderDO);
        System.out.println(String.format("订单[%s]创建成功:", orderDO.getOrderNo()));
        return orderId;
    }

    /**
     * 确认订单
     *
     * @param orderId
     */
    @Override
    public void confirmOrder(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println(String.format("确认订单,订单号:【%s】" ,order.getOrderNo()));
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.CONFIRMED).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 确认订单失败, 状态异常,订单号:" + order.getOrderNo());
        }
    }

    /**
     * 订单发货
     *
     * @param orderId
     */
    @Override
    public void deliver(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("订单出库,当前订单号:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.DELIVERY).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 订单出库失败, 状态异常,订单号:" + order.getOrderNo());
        }
    }

    /**
     * 签收订单
     *
     * @param orderId
     */
    @Override
    public void signOrder(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("订单签收,订单号:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.RECEIVED).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 订单签收失败, 状态异常,订单号:" + order.getOrderNo());
        }
    }

    /**
     * 确认完成
     *
     * @param orderId
     */
    @Override
    public void finishOrder(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("订单完成,订单号:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.CONFIRMED_FINISH).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 订单完成失败, 状态异常,订单号:" + order.getOrderNo());
        }
    }

    /**
     * 获取所有订单信息
     */
    @Override
    public List<OrderDO> listOrders() {
        return new ArrayList<>(ORDER_MAP.values());
    }

    @Autowired
    public void setStateEventUtil(StateEventUtil stateEventUtil) {
        this.stateEventUtil = stateEventUtil;
    }
}

订单对象

import com.congge.config.OrderStatusEnum;

public class OrderDO {

    private OrderStatusEnum orderStatusEnum;

    private Object orderNo;

    private long orderId;

    public void setOrderStatusEnum(OrderStatusEnum orderStatusEnum) {
        this.orderStatusEnum = orderStatusEnum;
    }

    public OrderStatusEnum getOrderStatusEnum() {
        return orderStatusEnum;
    }

    public Object getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(Object orderNo) {
        this.orderNo = orderNo;
    }

    public void setOrderId(long orderId) {
        this.orderId = orderId;
    }

    public long getOrderId() {
        return orderId;
    }
}

3.1.9 测试接口

添加一个测试的接口,在测试接口中,开启了 两个线程,理论上讲,不同的线程处理有快有慢,但是各自处理订单时互不影响,所以对每个线程来讲,都能得到订单处理的完整流程。

import com.congge.entity.OrderDO;
import com.congge.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    //localhost:8088/order/machine/test
    @GetMapping("/machine/test")
    public void testOrderStatusMachine(){
        Long orderId1 = orderService.createOrder(new OrderDO());
        Long orderId2 = orderService.createOrder(new OrderDO());

        orderService.confirmOrder(orderId1);
        new Thread("客户线程1"){
            @Override
            public void run() {
                orderService.deliver(orderId1);
                orderService.signOrder(orderId1);
                orderService.finishOrder(orderId1);
            }
        }.start();

        orderService.confirmOrder(orderId2);
        orderService.deliver(orderId2);
        orderService.signOrder(orderId2);
        orderService.finishOrder(orderId2);

        System.out.println("全部订单状态:" + orderService.listOrders());
    }
}

启动工程之后,调用接口,http://localhost:8088/order/machine/test,观察控制台输出日志信息,从输出的日志信息来看,与上述我们的预期也是一致的,订单的状态在不同的线程中各自执行,互不干扰。

3.2 待优化建议

3.2.1 状态持久化存储

上述代码中,状态的存储是基于JVM内存的,如果发生意外,状态数据将会丢失,实际业务中,可以考虑采用redis或mysql进行存储。

3.2.2 异常处理

在上面的逻辑中,状态机逻辑内部,对异常没有做处理,在状态转换过程中可能出现异常情况,需要适当地捕获和处理这些异常,防止状态机进入无效状态。

3.2.3 增加监控与审计

在实际应用中,为了便于调试和追溯,可以考虑集成日志记录或事件监听器来记录状态机的每一次状态变迁。

3.2.4 扩展性与维护性设计

伴随着业务的发展,后续系统中将会增加更多的状态机,因此状态机的设计应当具有足够的灵活性,以便于新增状态或调整转换规则。

四、写在文末

状态机模式的这一思想在java很多技术组件中均有体现,比如流程引擎,一些编排用的框架,甚至像设计模式中的模板模式等,合理使用状态机可以让复杂的状态切换转换为模板性的操作步骤,省去了复杂的业务逻辑编写,大大提升效率,本文到此结束,感谢观看。

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

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

相关文章

GEE23:基于植被物候实现农作物分类

地物分类 1. 写在前面2. 北京作物分类 1. 写在前面 今天分享一个有意思的文章&#xff0c;用于进行农作物分类。文章提出了一个灵活的物候辅助监督水稻(PSPR)制图框架。主要是通过提取植被物候&#xff0c;并自动对物候数据进行采样&#xff0c;获得足够多的样本点&#xff0c;…

LLM--提示词Propmt的概念、作用及如何设计提示词

文章目录 1. 什么是提示词&#xff1f;2. 提示词的作用3. 如何设计提示词&#xff1f;3.1. 提供详细的信息3.2. 指定角色3.3. 使用分隔符和特殊符号3.4. 提供示例3.5. 少量示例的思维链&#xff08;COT&#xff09;模型3.6. 思维树&#xff08;TOT&#xff09;模型3.7. 自洽性 …

牛角工具箱源码 轻松打造个性化在线工具箱

&#x1f389; Whats this&#xff1f; 这是一款在线工具箱程序&#xff0c;您可以通过安装扩展增强她的功能 通过插件模板的功能&#xff0c;您也可以把她当做网页导航来使用~ 觉得该项目不错的可以给个Star~ &#x1f63a; 演示地址 https://tool.aoaostar.com &#x1f…

京东云免费服务器申请入口,2024年最新免费云主机

京东云服务器免费6月申请入口 jdyfwq.com 在京东云免费云主机申请页面&#xff0c;免费云服务器配置为云主机2核4G5M和轻量云主机2C2G可以申请免费使用&#xff0c;目前京东云免费云服务器申请时长从之前的6个月缩短到1个月&#xff0c;如下图&#xff1a; 京东云免费云主机 云…

如何通过cookie来区分这是瑞数反爬的几代

一、以下仅个人观点&#xff0c;可能有误 1、瑞数反爬了解 瑞数反爬&#xff1a;大多数首次不带cookie的请求&#xff0c;响应状态码是202/412瑞数的cookie &#xff1a; 我们看PPT结尾的Cookie的来定位是几代&#xff0c;PT的是js生成的&#xff1b; 不看OS的&#xff0c;OS…

C语言使用STM32开发板手搓高端家居洗衣机

目录 概要 成品效果 背景概述 1.开发环境 2.主要传感器。 技术细节 1. 用户如何知道选择了何种功能 2.启动后如何进行洗衣 3.如何将洗衣机状态上传至服务器并通过APP查看 4.洗衣过程、可燃气检测、OLED屏显示、服务器通信如何并发进行 小结 概要 本文章主要是讲解如…

物联网学习1、什么是 MQTT?

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎&#xff0c;能够实现传感器、执行器和其它设备之间的高效通…

【Monero】Wallet RPC | Wallet CLI | 门罗币命令行查询余额、种子、地址等命令方法教程

ubuntu22.04 首先在运行daemon&#xff0c;详细安装运行教程可参考&#xff1a;The Monero daemon (monerod) ./monerodWallet CLI run ./monero-wallet-cli如果还没有钱包就根据提示创建钱包即可 输入密码 查询余额 balance查询种子 seed其他可执行命令操作&#xff1…

Linux:查看系统各个组件性能的方法

查看cpu top 还有更为直观的 htop 可以同时看到&#xff0c;内存占用&#xff0c;cpu占用&#xff0c;交换内存的占用 vmstat 是比较综合的可以看到内存&#xff0c;交换内存&#xff0c;io吞吐&#xff0c;系统&#xff0c;cpu 查看内存 free -h 可以看懂内存的使用情况 …

企业知识库搭建不再是难题,靠这几个软件就可以了

在当今知识为王的时代&#xff0c;具备一套强大且实用的企业知识库&#xff08;Knowledge Base&#xff09;已成为提升工作效率、促进团队合作不可或缺的工具。那么&#xff0c;问题来了&#xff0c;我们该如何搭建一套属于自己的知识库呢&#xff1f;今天&#xff0c;我就给大…

Spring IoCDI(3)

DI详解 接下来学习一下依赖注入DI的细节. 依赖注入是一个过程, 是指IoC容器在创建Bean时, 去提供运行时所依赖的资源, 而资源指的就是对象. 在之前的案例中, 使用了Autowired这个注解, 完成了依赖注入这个操作. 简单来说, 就是把对象取出来放到某个类的属性中. 在一些文章中…

C++学习随笔(8)——模板初阶

本章我们来学习一下C的模版部分&#xff01; 目录 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.1 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3. 类模板 3.1 类模板的定义格式 3.2 类模板的实例化 1. 泛型编程 如何实现一个通…

【C++】为什么能实现函数重载

从C语言一路学到C的途中&#xff0c;C语言C语言相比&#xff0c;多了个函数重载&#xff0c;那么函数重载是如何实现的呢&#xff0c;为什么C语言无法支持&#xff0c;在本篇博客中&#xff0c;将会讲解C为何能实现函数重载。 一.编译过程 C能实现函数重载&#xff0c;而C语言不…

CloudFlare WARP+ 无限流量

Cloudflare WARP 是一种由 Cloudflare 提供的虚拟专用网络&#xff08;VPN&#xff09;服务&#xff0c;旨在提供更安全、更快速的互联网连接。WARP 的目标是通过使用 Cloudflare 的全球网络基础设施来加密和保护用户的互联网流量&#xff0c;并且通过优化路由和连接&#xff0…

012_control_flow_in_Matlab中的控制流

Matlab中的控制流 虽然&#xff0c;我们说Matlab中的计算是向量化的&#xff0c;但是在某些情况下&#xff0c;作为一个“程序设计语言”&#xff0c;Matlab也提供了一些控制流结构&#xff0c;来帮助我们实现一些复杂的逻辑。 我会在介绍控制流的时候&#xff0c;提醒如何用…

Spark源码(二)-Netty简介

一、Netty简介 Netty 是一个异步事件驱动的网络通信应用框架&#xff0c;用于快速开发可维护的高性能服务器和客户端。简单地说Netty封装了JDK的NIO&#xff0c;不用再写一大堆复杂的代码&#xff0c;从NIO各种繁复的细节中脱离出来&#xff0c;让开发者重点关心业务逻辑。 二…

新书速递——《可解释AI实战(PyTorch版)》

本书旨在帮助你实施最新的可解释AI技术&#xff0c;以构建公平且可解释的AI系统。可解释AI是当今AI研究中的热门话题&#xff0c;但只有少数资源和指南涵盖了所有重要技术&#xff0c;这些技术对实践者来说非常有价值。本书旨在填补这一空白。 本书读者对象 本书既适合那些有兴…

阿里云2核4G云服务器支持多少人同时在线?并发数计算?

阿里云2核4G服务器多少钱一年&#xff1f;2核4G配置1个月多少钱&#xff1f;2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年。可以在阿里云CLUB中心查看 aliyun.club 当前最新2核4G服务器精准报价、优惠券和活动信息。 阿里云官方2…

04 | Swoole 源码分析之 epoll 多路复用模块

首发原文链接&#xff1a;Swoole 源码分析之 epoll 多路复用模块 大家好&#xff0c;我是码农先森。 引言 在传统的IO模型中&#xff0c;每个IO操作都需要创建一个单独的线程或进程来处理&#xff0c;这样的操作会导致系统资源的大量消耗和管理开销。 而IO多路复用技术通过…

第十四届蓝桥杯JavaA组省赛真题 - 棋盘

解题思路&#xff1a; 暴力 棋盘类题目取反操作&#xff1a; f[a][b]^1; 或者f[a][b] 1 - f[a][b]; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);int n scan.nextInt();int m scan.nex…