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

一、前言

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

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

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

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

相关文章

基于java+springboot+vue实现的付费自习室管理系统(文末源码+Lw+ppt)23-400

摘 要 付费自习室管理系统采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了springboot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对用户…

JavaSE类和对象

目录 1.面向对象 1.1面向对象的过程 2.类的定义和使用 2.1定义 2.2使用 2.2.1实例化 2.2.2访问类中数据 2.3类和对象说明 3.this引用 4.对象的构造及初始化 4.1初始化对象 4.2构造方法 4.2.1特性 4.3默认初始化 4.4就地初始化 5.封装 5.1概念 ​编辑 5.2访问限定…

ky9250(mpu9250)取得原始数据后通过简易卡尔曼滤波获取角度

我们通过ky9250(mpu9250)取得原始数据后&#xff08;gx,gy,gz,ax,ay,az,mx,my,mz&#xff09;后想通过原始数据解算角度姿态信息(想试验各种算法比如卡尔曼、mahony,Madgwick)&#xff0c;现将使用简易卡尔曼滤波获取姿态角度roll,pitch,yaw的方法介绍如下&#xff1a; 未完 稍…

探索C语言中的联合体和枚举:让处理数据更加得心应手

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;http://t.csdnimg.cn/Oytke 小新的主页&#xff1a;编程版小新-CSDN博客 C语言中有内置类型&#xff0c; 比如&…

vue3封装Element表格

配置表头配置多选配置序号自定义操作列按钮 封装表格 Table.vue <template><el-table:data"tableData"width"100%":maxHeight"maxHeight"v-bind"$attrs"selection-change"handleSelectChange"row-click"hand…

【Python】Scrapy 爬虫(简单了解)

Scrapy项目开发流程 1.创建项目 打开cmd scrapy startproject example 就可以创建一个Scrapy项目。这时&#xff0c;我们找到并打开这个文件。 复制路径&#xff1a;C:\Users\25194\example 复制到pycharm里面&#xff0c;打开该项目。 文件介绍 scrapy.cfg setting表明项…

竞赛课第四周(八数码问题+八皇后问题)

目的&#xff1a; 1. 掌握递归和排序 2. 掌握BFS与队列 3. 掌握DFS和递归 4. 熟悉并理解回溯问题 实验内容&#xff1a; 1.八数码问题&#xff1a; 在一个33的棋盘上&#xff0c;放置编号为1~8的8个方块&#xff0c;每个占一格&#xff0c;另外还有一个空格。与空格相邻…

基于ssm的平面设计课程在线学习平台系统(java项目+文档+源码)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的平面设计课程在线学习平台系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 前台功能&#xf…

(南京观海微电子)——DDIC显示触控芯片介绍

显示驱动芯片&#xff08;Display Driver Integrated Circuit&#xff0c;简称DDIC&#xff09;的主要功能是控制OLED显示面板。它需要配合OLED显示屏实现轻薄、弹性和可折叠&#xff0c;并提供广色域和高保真的显示信号。同时&#xff0c;OLED要求实现比LCD更低的功耗&#xf…

【保姆级教程】YOLOv3图像目标检测:训练自己的数据集

一、YOLOv3图像目标检测原理 二、YOLOv3代码及预训练权重下载 2.1 下载yolov3代码 这里使用的是B站大佬Bubbliiiing复现的yolov3代码 仓库地址&#xff1a; https://github.com/bubbliiiing/yolo3-pytorch 2.2 下载模型预训练权重unet_resnet_medical.pth 链接&#xff1a…

网络基础(二)——HTTP协议

目录 1、2个简单的预备知识 2、HTTP请求和响应的格式 3、实现一个最简单的httpserver 4、HTTP的细节字段 4.1、GET和POST 4.2、HTTP的状态码 4.3、HTTP常见Header 1、2个简单的预备知识 首先我们来看一个域名&#xff1a;http://www.baidu.com/&#xff0c;很明显这是百…

实验三智能手机互联网程序设计(微信程序方向)实验报告

实验目的和要求 请编写下方商品列表页面&#xff0c;展示商品名称和价格&#xff1b; 二、实验步骤与结果&#xff08;给出对应的代码或运行结果截图&#xff09; Index.WXML <view class"shop" wx:for"{{10}}"> <vie…

40.网络游戏逆向分析与漏洞攻防-角色管理功能通信分析-角色删除功能的数据包失败的分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a; 易道云信息技术研究院VIP课 上一个内容&#xff1a;39.角色数据的维…

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

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

SQLite版本3中的文件锁定和并发(七)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;自己编译SQLite或将SQLite移植到新的操作系统&#xff08;六&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 正文&#xff1a; 1.0 SQLite 版本 3 中的文件锁定和并发 SQLite 版本 3.0.0 引入了新的锁…

【蓝桥杯嵌入式】六、真题演练(一)-1演练篇:第 届真题

温馨提示&#xff1a; 真题演练分为模拟篇和研究篇。本专栏的主要作用是记录我的备赛过程&#xff0c;我打算先自己做一遍&#xff0c;把遇到的问题和不同之处记录到演练篇&#xff0c;然后再返回来仔细研究一下&#xff0c;找到最佳的解题方法记录到研究篇。 解题记录&#x…

Yarn的安装及使用(1):安装

一、Yarn的安装 在不同操作系统上安装Yarn的步骤和注意事项&#xff1a; 1、Windows 1.1 通过.msi安装程序安装&#xff1a; 步骤&#xff1a; 访问 Yarn官方网站 下载适用于Windows的.msi安装包。 运行下载好的.msi文件&#xff0c;按照向导进行安装。 在安装过程中&#…

Apache Hive的基本使用语法(一)

一、数据库操作 创建数据库 create database if not exists myhive;查看数据库 use myhive; desc database myhive;创建数据库并指定hdfs存储 create database myhive2 location /myhive2;删除空数据库&#xff08;如果有表会报错&#xff09; drop database myhive;…

爱上数据结构:栈和队列的概念及使用

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;数据结构 ​ 一、栈 1.栈的基本概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;…

酒店管理系统项目用例图及用例说明

1、系统功能模块图 2、部分系统功能模块说明 &#xff08;1&#xff09;查询房间剩余 模块名称&#xff1a;管理员登录 编号&#xff1a;1-1 主要功能&#xff1a;验证管理员登录用户名及密码 上级调用模块&#xff1a;无 下级调用模块&#xff1a; 约束&#xff1a; &a…