【微服务】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 异常*/@Overridepublic 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 通过什么事件触发*/@Overridepublic 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*/@Beanpublic DefaultStateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister(){Map<OrderDO, StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum>> map = new HashMap<>();return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO>() {@Overridepublic void write(StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> context, OrderDO order) throws Exception {//持久化操作map.put(order, context);}@Overridepublic 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 {@Resourceprivate StateMachine<OrderStatusEnum, OrderStatusOperateEventEnum> orderStateMachine;@Resourceprivate 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*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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());}}/*** 获取所有订单信息*/@Overridepublic List<OrderDO> listOrders() {return new ArrayList<>(ORDER_MAP.values());}@Autowiredpublic 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 {@Autowiredprivate 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"){@Overridepublic 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很多技术组件中均有体现,比如流程引擎,一些编排用的框架,甚至像设计模式中的模板模式等,合理使用状态机可以让复杂的状态切换转换为模板性的操作步骤,省去了复杂的业务逻辑编写,大大提升效率,本文到此结束,感谢观看。
相关文章:
 
【微服务】spring状态机模式使用详解
一、前言 在很多系统中,通常会涉及到某个业务需要进行各种状态的切换操作,例如在审批流程场景下,某个审批的向下流转需要依赖于上一个状态的结束,再比如电商购物场景中,一个订单的生命周期往往伴随着不同的状态&#…...
 
【算法刷题day14】Leetcode:144.二叉树的前序遍历、94.二叉树的中序遍历、145.二叉树的后序遍历
文章目录 二叉树递归遍历解题思路代码总结 二叉树的迭代遍历解题思路代码总结 二叉树的统一迭代法解题思路代码总结 草稿图网站 java的Deque 二叉树递归遍历 题目: 144.二叉树的前序遍历 94.二叉树的中序遍历 145.二叉树的后序遍历 解析:代码随想录解析…...
mysql闲谈
如何定位慢查询 1、测试环境压测时,有的接口非常慢,响应时间超过2秒以上。当时系统部署了运维的监控系统Skywalking,在展示报表中可以看到是哪儿个接口慢,可以看到SQL具体执行时间。 2、如果没有类似的监控系统,在Mysq…...
 
物联网学习1、什么是 MQTT?
MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通…...
 
【机器学习】数据探索(Data Exploration)---数据质量和数据特征分析
一、引言 在机器学习项目中,数据探索是至关重要的一步。它不仅是模型构建的基础,还是确保模型性能稳定、预测准确的关键。数据探索的过程中,数据质量和数据特征分析占据了核心地位。数据质量直接关系到模型能否从数据中提取有效信息ÿ…...
软件测试(一)--简介+主流技能+分类+模型+流程
一、软件及测试简介 1、软件生产过程 需求产生–需求文档–设计效果图–产品开发–产品测试(测试产品与需求文档是否一致)–部署上线 2、什么是软件测试 使用技术手段验证软件是否满足使用需求。 技术包括:(使用网络技术测试安…...
 
技术引领,策略升级:腾讯云与你共探数字金融新篇章
引言 2024 年 3 月 27 日下午,在北京腾讯总部,一场关于大模型与数据要素时代数字金融发展的深入讨论火热进行中。【TVP 走进腾讯:大模型与数据要素时代的数字金融发展论坛】是在腾讯二十年发展历程和数字化实践的基础上,进一步探索…...
 
数据库-root密码丢失的重置方案(win11环境)
当在windows系统中安装的mysql由于操作不当,或者密码遗忘,今天测试了一下,可以用以下方法重置root的密码。 mysqlwindows环境root密码重置问题 在win10/11环境下mysql8密码遗忘后的重置密码方案。 停止mysql服务 查找windows中的mysql服务名称…...
免试生常问的一些问题汇总---专升本学习篇
1.你怎么理解你申请的专业? 答:软件工程室一门涉及软件开发、维护和管理的工程学科。它结合了计算机科学、工程学和管理科学的原理,皆在通过系统化、规范化的方法来开发高质量的软件系统。 1.技术和支持 :软件工程包括编程语言、算法、数据结构、软件设计模式、软件测试、…...
FPGA的就业前景
FPGA(Field-Programmable Gate Array)技术在数字电路设计和嵌入式系统开发方面具有广泛的应用,因此在FPGA领域有着较好的就业前景。 目前,FPGA在通信、计算机、消费电子、汽车、航空航天等行业中得到了广泛应用。随着新一代通信网…...
7.阻塞模式与非阻塞模式
1.阻塞模式 一个线程来处理多个连接显得力不从心 accept等待连接 是一个阻塞方法 read读取SocketChannel中的数据 是一个阻塞方法 /*** 服务端* param args* throws IOException*/public static void main(String[] args) throws IOException {//建立一个缓冲区ByteBuffer b…...
 
Unity类银河恶魔城学习记录11-10 p112 Items drop源代码
Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili ItemObject_Trigger.cs using System.Collections; using System.Collecti…...
 
EasyExcel 模板导出excel、合并单元格及单元格样式设置。 Freemarker导出word 合并单元格
xls文件: 后端代码: InputStream filePath this.getClass().getClassLoader().getResourceAsStream(templateFile);// 根据模板文件生成目标文件ExcelWriter excelWriter EasyExcel.write(orgInfo.getFilename()).excelType(ExcelTypeEnum.XLS).withTe…...
 
炫我科技:云渲染领域的佼佼者
随着数字化时代的来临,云渲染技术正逐渐成为影视、游戏、动画等创意产业的重要支柱。在这一领域中,炫我科技凭借其卓越的技术实力、优质的服务以及不断创新的精神,已然成为了云渲染行业的佼佼者。 炫我科技自成立之初,便以打造高…...
 
VsCode正确解决vue3+Eslint+prettier+Vetur的配置冲突
手把手教你VsCode正确解决vue3EslintprettierVetur的配置冲突 VsCode正确解决vue3EslintprettierVetur的配置冲突Eslint文档查看和修改规则:step1:首先快速浏览下规则简要setp2: ctrlF 搜索你要配置规则的英文名,例如attributesetp3: 修改配置…...
 
计算机网络—VLAN 间路由配置
目录 1.拓扑图 2.实验环境准备 3.为 R3 配置 IP 地址 4.创建 VLAN 5.配置 R2 上的子接口实现 VLAN 间路由 6.配置文件 1.拓扑图 2.实验环境准备 配置R1、R3和S1的设备名称,并按照拓扑图配置R1的G0/0/1接口的IP地址。 [Huawei]sysname R1 [R1]interface Giga…...
微服务篇-C 深入理解第一代微服务(SpringCloud)_VII 深入理解Swagger接口文档可视化管理工具
原创作者:田超凡(程序员田宝宝) 版权所有,引用请注明原作者,严禁复制转载 Part 1 理论部分 1 传统API接口文档存在的问题? 1 对API接口文档进行更新的时候,需要及时将变化通知前端开发人员&…...
区块链的应用领域:重塑未来的信任机制
区块链作为一种新兴的技术,正在逐渐改变我们的生活。它以其独特的优势,正在开启一个信任的新时代。在金融、供应链管理、医疗健康、教育、文化娱乐、房地产等众多领域,区块链已经崭露头角,以其独特的方式发挥着作用。 1.金融领域…...
怎么在循环List的时候删除List的元素
怎么在循环List的时候删除List的元素 1. 先给出结论 任何时候都不要在 for 循环中删除 List 集合元素 2. 为什么在 for 循环中删除 List 集合元素是错误的 在 for 循环中删除 List 集合元素的问题主要是因为循环的迭代器和 List 集合的元素索引之间的冲突。在使用 for 循环遍历…...
 
SpringBoot+thymeleaf完成视频记忆播放功能
一、背景 1)客户要做一个视频播放功能,要求是系统能够记录观看人员在看视频时能够记录看到了哪个位置,在下次观看视频的时候能够从该位置进行播放。 2)同时,也要能够记录是谁看了视频,看了百分之多少。 说明:由于时间关系和篇幅原因,我们这里只先讨论第一个要求,第…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。  - 个性化梦境…...
 
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
 
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
 
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
 
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
 
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
