|
聊一聊 Spring StateMachine 的基本概念和实践
glmapper
磊叔的技术博客
磊叔的技术博客 聊一聊技术、聊一聊生活 69篇内容
2024年09月18日 09:29
安徽
在之前的一些项目实践中,关于状态变更流转基本都是通过业务逻辑+更新表的方式来实现的;这种实现方式会在代码中产生较多的条件语句,从可读性上来说还算不错。近期项目中又涉及到一个状态流转的功能需求,因此笔者就期望借此来了解下状态机相关的机制和使用。笔者是基于spring statem)achine进行的调研;在查找相关资料和构建demo的过程中发现,网络上关于spring statemachine的一些介绍和使用,除了官方文档在概念上有比较全的概述之外,其他的均不能提供很好的入门指引,特别是在持久化部分。这也是笔者将本篇文章分享出来的原因,期望给各位读者提供一个比较完整的入门和应用案例(此瓜包熟)。本篇文章中笔者使用的版本是3.2.1,springboot版本是2.4.12,jdk版本是8。下面是官方文档的链接地址:Spring Statemachine - Reference Documentation关于spring statemachine的介绍本篇不再赘述,为了便于理解和阅读的连贯性,下面会将几个比较重要的概念先抛出来,接着是 step by step 的构建一个完整的spring statemachine案例。基本概念在Spring StateMachine中,下表的概念共同构成了状态机的核心结构。以下是对每个概念的解释。概念定义解释示例State (状态)代表状态机中的一个具体状态。在状态机中,状态是系统在某一时刻的条件或情境。1、每个状态可以定义进入 (entry) 和退出 (exit) 时的行为。2、一个状态可以是终态 (end state),当状态机到达这个状态时,状态机的生命周期就结束了。在订单处理中UNPAID和WAITING_FOR_RECEIVETransition(状态转换)表示状态机从一个状态到另一个状态的转换条件。通常伴随着一个事件 (event) 的发生。1、一个Transition通常会绑定一个事件 (Event)2、Transition还可以有条件 (Guard) 和动作 (Action) 绑定。订单处理中UNPAID到WAITING_FOR_RECEIVE由 PAY 事件触发Action (动作)在状态转换过程中执行的操作。它可以在状态转换时触发,或者在进入或退出状态时触发。1、Action可以在状态转换之前 (before transition) 或之后 (after transition) 执行2、Action是业务逻辑的具体表现,比如记录日志、更新数据库、发送通知等。订单状态从UNPAID变为WAITING_FOR_RECEIVE时,Action可以发送短信通知给用户。Guard (守卫条件)一个布尔表达式,用于判断状态转换是否允许执行。它决定了在事件触发时,是否允许从一个状态转换到另一个状态。1、Guard返回true,则状态转换可以执行。2、Guard返回false,则状态转换不可以执行。一个Guard可以检查订单是否已经完成支付,只有当订单支付完成时,才允许状态从UNPAID转换到WAITING_FOR_RECEIVE。Region (区域)状态机中的一个子状态机,可以看作是状态机内部的一个独立区域,允许并行状态和多个子状态的存在。1、Region允许状态机在不同的区域中同时处于不同的状态。2、支持复杂的状态模型,比如并行状态或层次化状态。如果一个订单在处理的过程中可以同时进入“支付”流程和“物流”流程,这两个流程可以定义为两个并行的Region。StateMachine(状态机)StateMachine是整个状态机的核心组件,管理状态、事件和状态转换。它封装了所有状态、转换、动作、守卫条件等的定义和执行逻辑。1、StateMachine控制状态的变化和事件的处理。2、可以监听状态变化和转换,提供钩子来执行特定的业务逻辑。-案例构建本小节就是 step by step 构建一个状态机。这个过程是不断演进的,从一个基本的状态机、到添加 Action、Guard、异常处理以及持久化。状态及事件定义状态机中基本的元素是状态和事件,整个业务逻辑的组织变更流转基本是围绕状态和事件展开。 States 状态枚举类/***@ClassnameState*@Description状态枚举*@Date2024/8/1613:53*@Createdbyglmapper*/publicenumStates{//未支付UNPAID,//待审核WAITING_FOR_CHECK,//待收货WAITING_FOR_RECEIVE,//结束DONE;} Events 事件枚举类/***@ClassnameEvent*@Description事件枚举*@Date2024/8/1613:53*@Createdbyglmapper*/publicenumEvents{PAY,//支付RECEIVE//收货}状态机定义用于开启和配置状态机状态以及状态转换条件、动作以及守卫等packageorg.glmapper.techssm.configs;//考虑到很多网上的案例中没有提供准确的import,笔者这里将import也放出来//除org.glmapper.techssm开头的是项目自己的之外,其他的均为三方依赖引入importorg.glmapper.techssm.actions.ErrorHandlerAction;importorg.glmapper.techssm.actions.OrderIdCheckFailedAction;importorg.glmapper.techssm.actions.OrderIdCheckPassedAction;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.glmapper.techssm.guards.OrderIdCheckGuard;importorg.springframework.context.annotation.Configuration;importorg.springframework.statemachine.config.EnableStateMachine;importorg.springframework.statemachine.config.StateMachineConfigurerAdapter;importorg.springframework.statemachine.config.builders.StateMachineStateConfigurer;importorg.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;importjava.util.EnumSet;/***@ClassnameStateMachineConfig*@Description状态机配置类*@Date2024/8/1613:54*@Createdbyglmapper*/@Configuration@EnableStateMachine//该注解用来启用SpringStateMachine状态机功能publicclassStateMachineConfigextendsStateMachineConfigurerAdapter{/***初始化当前状态机有哪些状态**@paramstatesthe{@linkStateMachineStateConfigurer}*@throwsException*/@Overridepublicvoidconfigure(StateMachineStateConfigurerstates)throwsException{states.withStates().initial(States.UNPAID)//指定初始状态为未支付.choice(States.WAITING_FOR_CHECK)//指定状态为待审核,这里是个选择状态.states(EnumSet.allOf(States.class));//指定States中的所有状态作为该状态机的状态定义}/***初始化当前状态机有哪些状态迁移动作,有来源状态为source,目标状态为target,触发事件为event*
*1、UNPAID->WAITING_FOR_CHECK事件PAY*2、WAITING_FOR_CHECK*->WAITING_FOR_RECEIVE检查通过*->UNPAID检查未通过*3、WAITING_FOR_RECEIVE->DONE事件RECEIVE**@paramtransitionsthe{@linkStateMachineTransitionConfigurer}*@throwsException*/@Overridepublicvoidconfigure(StateMachineTransitionConfigurertransitions)throwsException{transitions.withExternal().source(States.UNPAID).target(States.WAITING_FOR_CHECK).event(Events.PAY).and().withChoice().source(States.WAITING_FOR_CHECK).first(States.WAITING_FOR_RECEIVE,newOrderIdCheckGuard(),newOrderIdCheckPassedAction(),newErrorHandlerAction())//如判断为true->待收货状态.last(States.UNPAID,newOrderIdCheckFailedAction()).and().withExternal().source(States.WAITING_FOR_RECEIVE).target(States.DONE).event(Events.RECEIVE);//收货事件将触发:待收货状态->结束状态}}StateMachineConfig这个类主要用于定义状态机的核心结构,包括状态(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)): 如果需要显式指定状态机启动时的初始状态,可以在该方法中设置。OrderCheckGuard 定义OrderIdCheckGuard的作用是检查当前订单号是否合法,本例中如果订单号小于 100 则认为是非法的订单号,则不允许通过。importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.glmapper.techssm.models.Order;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.statemachine.StateContext;importorg.springframework.statemachine.guard.Guard;/***@ClassnameOrderIdCheckGuard*@Description要求订单从“待支付”变为“待收货”状态需要满足某个条件(这里为方便演示,只有订单id不小于100的才满足条件)*@Date2024/8/1614:27*@Createdbyglmapper*///订单检查守卫publicclassOrderIdCheckGuardimplementsGuard{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger("SM");//检查方法@Overridepublicbooleanevaluate(StateContextcontext){//获取消息中的订单对象Orderorder=(Order)context.getMessage().getHeaders().get("order");//订单号长度不等于10位,则订单号非法if(String.valueOf(order.getId()).length()!=10){LOGGER.info("检查订单:不通过,不合法的订单号:"+order.getId());returnfalse;}else{LOGGER.info("检查订单:通过");returntrue;}}}Action 定义这里的 action 包括针对检查成功和检查失败两个分支逻辑的处理,OrderIdCheckPassedAction和OrderIdCheckFailedAction OrderIdCheckPassedActionpackageorg.glmapper.techssm.actions;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.glmapper.techssm.models.Order;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.statemachine.StateContext;importorg.springframework.statemachine.action.Action;/***@ClassnameOrderCheckPassedAction*@Description订单检查通过Action*@Date2024/8/1615:48*@Createdbyglmapper*/publicclassOrderIdCheckPassedActionimplementsAction{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger("SM");/***执行方法**@paramcontext状态上下文*/@Overridepublicvoidexecute(StateContextcontext){//获取消息中的订单对象Orderorder=(Order)context.getMessage().getHeaders().get("order");//设置新状态order.setStates(States.WAITING_FOR_RECEIVE);LOGGER.info("通过检查,等待收货......");}} OrderIdCheckFailedActionpackageorg.glmapper.techssm.actions;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.glmapper.techssm.models.Order;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.statemachine.StateContext;importorg.springframework.statemachine.action.Action;/***@ClassnameOrderCheckFailedAction*@Description订单检查未通过Action*@Date2024/8/1615:49*@Createdbyglmapper*/publicclassOrderIdCheckFailedActionimplementsAction{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger("SM");/***执行方法**@paramcontext状态上下文*/@Overridepublicvoidexecute(StateContextcontext){//获取消息中的订单对象Orderorder=(Order)context.getMessage().getHeaders().get("order");//设置新状态order.setStates(States.UNPAID);LOGGER.info("检查未通过,状态不流转......");}} action 异常处理importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.statemachine.StateContext;importorg.springframework.statemachine.action.Action;/***@ClassnameErrorHandlerAction*@Description如果action执行报错了,会执行此类的逻辑*@Date2024/8/1614:35*@Createdbyglmapper*/publicclassErrorHandlerActionimplementsAction{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger("SM");@Overridepublicvoidexecute(StateContextcontext){RuntimeExceptionexception=(RuntimeException)context.getException();LOGGER.error("捕获到异常:"+exception);//将发生的异常信息记录在StateMachineContext中,在外部可以根据这个这个值是否存在来判断是否有异常发生。context.getStateMachine().getExtendedState().getVariables().put(RuntimeException.class,exception);}}配置一个状态变换监听器状态机监听器的实现方式有两种,一种是通过继承StateMachineListenerAdapter类实现,另一种是通过注解的方式。通过继承 StateMachineListenerAdapterpackageorg.glmapper.techssm.listener;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.statemachine.listener.StateMachineListenerAdapter;importorg.springframework.statemachine.transition.Transition;importorg.springframework.stereotype.Component;/***@ClassnameOrderStateMachineListener*@Description基于StateMachineListenerAdapter的状态机监听器实现方式*@Date2024/8/2015:33*@Createdbyglmapper*/@ComponentpublicclassOrderStateMachineListenerextendsStateMachineListenerAdapter{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(OrderStateMachineListener.class);/***在状态机进行状态转换时调用**@paramtransitionthetransition*/@Overridepublicvoidtransition(Transitiontransition){//当前是未支付状态if(transition.getTarget().getId()==States.UNPAID){LOGGER.info("订单创建");}//从未支付->待收货状态if(transition.getSource().getId()==States.UNPAID&transition.getTarget().getId()==States.WAITING_FOR_RECEIVE){LOGGER.info("用户支付完毕");}//从待收货->完成状态if(transition.getSource().getId()==States.WAITING_FOR_RECEIVE&transition.getTarget().getId()==States.DONE){LOGGER.info("用户已收货");}}/***在状态机开始进行状态转换时调用**@paramtransitionthetransition*/@OverridepublicvoidtransitionStarted(Transitiontransition){//从未支付->待收货状态if(transition.getSource().getId()==States.UNPAID&transition.getTarget().getId()==States.WAITING_FOR_RECEIVE){LOGGER.info("用户支付(状态转换开始)");}}/***在状态机进行状态转换结束时调用**@paramtransitionthetransition*/@OverridepublicvoidtransitionEnded(Transitiontransition){//从未支付->待收货状态if(transition.getSource().getId()==States.UNPAID&transition.getTarget().getId()==States.WAITING_FOR_RECEIVE){LOGGER.info("用户支付(状态转换结束)");}}}需要在状态机配置类中配置监听器@AutowiredprivateOrderStateMachineListenerlistener;//初始化当前状态机配置@Overridepublicvoidconfigure(StateMachineConfigurationConfigurerconfig)throwsException{//设置监听器config.withConfiguration().listener(listener);}使用注解(本例中使用的方式)packageorg.glmapper.techssm.configs;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.glmapper.techssm.models.Order;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.context.annotation.Configuration;importorg.springframework.messaging.Message;importorg.springframework.statemachine.annotation.OnTransition;importorg.springframework.statemachine.annotation.OnTransitionEnd;importorg.springframework.statemachine.annotation.OnTransitionStart;importorg.springframework.statemachine.annotation.WithStateMachine;/***@ClassnameStateMachineEventConfig*@Description基于注解的事件监听器实现方式,可以同于替代OrderStateMachineListener*@Date2024/8/1614:16*@Createdbyglmapper*/@Configuration@WithStateMachinepublicclassStateMachineEventConfig{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(StateMachineEventConfig.class);@OnTransition(target="UNPAID")publicvoidcreate(){LOGGER.info("订单创建");}@OnTransition(source="UNPAID",target="WAITING_FOR_CHECK")publicvoidpay(Messagemessage){//获取消息中的订单对象Orderorder=(Order)message.getHeaders().get("order");//设置新状态order.setStates(States.WAITING_FOR_RECEIVE);LOGGER.info("用户支付完毕,状态机反馈信息:"+message.getHeaders().toString());}@OnTransition(source="WAITING_FOR_RECEIVE",target="DONE")publicvoidreceive(Messagemessage){//获取消息中的订单对象Orderorder=(Order)message.getHeaders().get("order");//设置新状态order.setStates(States.DONE);LOGGER.info("用户已收货,状态机反馈信息:"+message.getHeaders().toString());}//监听状态从待检查订单到待收货@OnTransition(source="WAITING_FOR_CHECK",target="WAITING_FOR_RECEIVE")publicvoidcheckPassed(){System.out.println("检查通过,等待收货");}//监听状态从待检查订单到待付款@OnTransition(source="WAITING_FOR_CHECK",target="UNPAID")publicvoidcheckFailed(){System.out.println("检查不通过,等待付款");}@OnTransitionStart(source="UNPAID",target="WAITING_FOR_RECEIVE")publicvoidpayStart(){LOGGER.info("用户支付(状态转换开始)");}@OnTransitionEnd(source="UNPAID",target="WAITING_FOR_RECEIVE")publicvoidpayEnd(){LOGGER.info("用户支付(状态转换结束)");}}@WithStateMachine是 Spring StateMachine 提供的一个注解,用于将某个类与状态机绑定。它的主要作用是在该类中自动注入状态机实例,并允许你在该类中监听和处理状态机的事件、状态变化等。使用 mongodb 持久化机制spring statemachine在外部化的持久化策略上提供了 3 种,包括 JPA、MongoDB 以及 Redis;具体可以参考:Repository Persistence。下面是本案例中使用MongoDbPersistingStateMachineInterceptor的实现。关于持久化下面笔者会单独做介绍。packageorg.glmapper.techssm.configs.persister;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Profile;importorg.springframework.statemachine.data.jpa.JpaPersistingStateMachineInterceptor;importorg.springframework.statemachine.data.jpa.JpaStateMachineRepository;importorg.springframework.statemachine.data.mongodb.MongoDbPersistingStateMachineInterceptor;importorg.springframework.statemachine.data.mongodb.MongoDbStateMachineRepository;importorg.springframework.statemachine.persist.DefaultStateMachinePersister;importorg.springframework.statemachine.persist.StateMachinePersister;importorg.springframework.statemachine.persist.StateMachineRuntimePersister;/***@ClassnameStateMachinePersistentConfig*@Description状态机持久化的配置类,自定义进行状态机持久化配置*@Date2024/8/1614:56*@Createdbyglmapper*/@ConfigurationpublicclassStateMachinePersistentConfig{@Configuration@Profile("mongo")publicstaticclassMongoStateMachinePersistConfig{@BeanpublicStateMachineRuntimePersisterstateMachineRuntimePersister(MongoDbStateMachineRepositorymongoDbStateMachineRepository){returnnewMongoDbPersistingStateMachineInterceptor(mongoDbStateMachineRepository);}@BeanpublicStateMachinePersisterstateMachinePersister(StateMachineRuntimePersisterstateMachineRuntimePersister){returnnewDefaultStateMachinePersister(stateMachineRuntimePersister);}}@Configuration@Profile("jpa")publicstaticclassJpaStateMachinePersistConfig{@BeanpublicStateMachineRuntimePersisterstateMachineRuntimePersister(JpaStateMachineRepositoryjpaStateMachineRepository){returnnewJpaPersistingStateMachineInterceptor(jpaStateMachineRepository);}@BeanpublicStateMachinePersisterstateMachinePersister(StateMachineRuntimePersisterstateMachineRuntimePersister){returnnewDefaultStateMachinePersister(stateMachineRuntimePersister);}}}测试这里面还涉及到几个类,这里笔者全部放出来,包括Order类、OrderStateService类、OrderStateController类和一个启动类。 Order@DatapublicclassOrder{//订单号privateintid;//订单状态privateStatesstates;publicOrder(intorderId){this.id=orderId;}publicOrder(){}@OverridepublicStringtoString(){return"订单号:"+id+", 订单状态:"+states;}} OrderStateServicepackageorg.glmapper.techssm.service;importorg.glmapper.techssm.enums.Events;importorg.glmapper.techssm.enums.States;importorg.glmapper.techssm.models.Order;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.messaging.Message;importorg.springframework.messaging.support.MessageBuilder;importorg.springframework.statemachine.StateMachine;importorg.springframework.statemachine.persist.StateMachinePersister;importorg.springframework.stereotype.Service;/***@ClassnameModelStateService*@DescriptionModelStateService*@Date2024/8/2110:14*@Createdbyglmapper*/@ServicepublicclassOrderStateService{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(OrderStateService.class);/***状态机*/@AutowiredprivateStateMachinestateMachine;/***状态机持久化器*/@AutowiredprivateStateMachinePersisterstateMachinePersister;publicStringcreateModel()throwsException{intorderId=getOrderId();Orderorder=newOrder();order.setStates(States.UNPAID);order.setId(orderId);this.stateMachine.start();this.stateMachinePersister.persist(stateMachine,String.valueOf(order.getId()));return"订单创建成功,订单号:"+orderId;}publicbooleanpay(intorderId){returnthis.sendMessages(newOrder(orderId),stateMachine,Events.PAY);}publicbooleanreceive(intorderId){returnthis.sendMessages(newOrder(orderId),stateMachine,Events.RECEIVE);}privatesynchronizedbooleansendMessages(Orderorder,StateMachinestateMachine,Eventsevent){LOGGER.info("---发送"+event+"事件---");try{stateMachinePersister.restore(stateMachine,String.valueOf(order.getId()));Messagemessage=MessageBuilder.withPayload(event).setHeader("order",order).build();//构建消息booleanresult=stateMachine.sendEvent(message);LOGGER.info("事件是否发送成功:"+result+",当前状态:"+stateMachine.getState().getId());stateMachinePersister.persist(stateMachine,String.valueOf(order.getId()));returnresult;}catch(Exceptione){e.printStackTrace();}returnfalse;}privateintgetOrderId(){//somelogichere,创建按时间递增的订单号,提供代码如下,不使用variantreturn(int)(System.currentTimeMillis()/1000);}} OrderStateController/***@ClassnameOrderStateController*@Description模型状态控制器*@Date2024/8/2110:12*@Createdbyglmapper*/@RestController@RequestMapping("/api/model/state")publicclassOrderStateController{@AutowiredprivateOrderStateServicemodelStateService;@RequestMapping("create")publicStringcreateModel(){returnthis.modelStateService.createModel();}@RequestMapping("pay")publicbooleanpay(@RequestParam("orderId")intorderId){returnthis.modelStateService.pay(orderId);}@RequestMapping("receive")publicbooleanreceive(@RequestParam("orderId")intorderId){returnthis.modelStateService.receive(orderId);}} TechSsmApplication@SpringBootApplicationpublicclassTechSsmApplicationimplementsCommandLineRunner{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TechSsmApplication.class);publicstaticvoidmain(String[]args){SpringApplication.run(TechSsmApplication.class,args);}}验证正常逻辑在启动程序之后分别执行OrderStateController 中的 create、pay 和 receive三个接口; 执行 create,日志输出如下:订单创建此时 mongodb 中的数据截图如下:image-20240822171155774 执行 pay,日志输出如下---发送PAY事件---用户支付完毕,状态机反馈信息:{order=订单号:1724317856, 订单状态:WAITING_FOR_RECEIVE, id=02bb9d45-901f-be53-b6d0-29a3a8b5e667, timestamp=1724317963599}检查订单:通过通过检查,等待收货......事件是否发送成功:true,当前状态:WAITING_FOR_RECEIVE此时 mongodb 中的数据截图如下:image-20240822171326244 执行 receive,日志输出如下:---发送RECEIVE事件---用户已收货,状态机反馈信息:{order=订单号:1724317856, 订单状态:DONE, id=dba29317-935b-7d3a-cafa-cdb443b5aab7, timestamp=1724318041106}事件是否发送成功:true,当前状态:DONE此时 mongodb 中的数据如下image-20240822171429430触发订单检查不通过前面提到的订单长度不能小于 10,这里需要在代码中魔改,假设订单号是 9999。执行结果大致如下:---发送PAY事件---用户支付完毕,状态机反馈信息:{order=订单号:65, 订单状态:WAITING_FOR_RECEIVE, id=563c41b5-eaea-6c39-cc83-87106acd0591, timestamp=1724318183133}检查订单:不通过,不合法的订单号:65检查未通过,状态不流转......事件是否发送成功:true,当前状态:UNPAID可以看到在执行了 PAY 事件时,因为订单号检查不通过,因此状态没有发生变化。至此案例部分就完结了。源码可以在我的掘金首页给我留言获取持久化和序列化先说序列化,官方文档中提到目前仅支持 kryo 进行序列化,笔者最开始在进行持久化的实现时踩坑无数,已经到修改源码构建自定义持久化机制的地步,因此就自然而然的用到了它提供的序列化器,这个在源码中是和 Repository 机制算是绑定的privatefinalStateMachineSerialisationServiceserialisationService;/***Instantiatesanewrepositorystatemachinepersist.*/protectedRepositoryStateMachinePersist(){this.serialisationService=newKryoStateMachineSerialisationService();}序列化就先暂放一边。来聊一下持久化。网上关于持久化大多是基于内存和 redis 的实现的,和官网上提供的基于拦截器的持久化方式不同。直接使用拦截器方式进行持久化首先是将OrderStateService中的sendMessages方法中进行持久化的相关逻辑注释掉privatesynchronizedbooleansendMessages(Orderorder,StateMachinestateMachine,Eventsevent){LOGGER.info("---发送"+event+"事件---");try{//stateMachinePersister.restore(stateMachine,String.valueOf(order.getId()));Messagemessage=MessageBuilder.withPayload(event).setHeader("order",order).build();//构建消息booleanresult=stateMachine.sendEvent(message);LOGGER.info("事件是否发送成功:"+result+",当前状态:"+stateMachine.getState().getId());//stateMachinePersister.persist(stateMachine,String.valueOf(order.getId()));returnresult;}catch(Exceptione){e.printStackTrace();}returnfalse;}然后配置状态机配置类中配置持久化,修改StateMachineConfig类,添加如下代码@AutowiredprivateStateMachineRuntimePersisterstateMachineRuntimePersister;@Overridepublicvoidconfigure(StateMachineConfigurationConfigurerconfig)throwsException{config.withPersistence().runtimePersister(stateMachineRuntimePersister);//setmachineIdornotconfig.withConfiguration().machineId("orderStateMachine").autoStartup(true);}测试步骤如下: 1、调用 create 接口创建一个新的订单和状态机 2、调用 pay 方法并且传入 1 中产生的订单 3、调用 create 接口创建一个新的订单和状态机 4、调用 receive 方法并且传入 1 中产生的订单 5、调用 pay 方法并且传入 3 中产生的订单输出日志如下(订单创建的日志因未涉及到状态流转,因此不会触发想要的事件监听执行):---发送PAY事件---//这里是步骤1订单的PAY用户支付完毕,状态机反馈信息:{order=订单号:1724379861, 订单状态:WAITING_FOR_RECEIVE, id=2b33c417-366e-03d3-a1d5-9c786d1cd669, timestamp=1724379883764}用户支付完毕,状态机反馈信息:{order=订单号:1724379861, 订单状态:WAITING_FOR_RECEIVE, id=2b33c417-366e-03d3-a1d5-9c786d1cd669, timestamp=1724379883764}检查订单:通过通过检查,等待收货......事件是否发送成功:true,当前状态:WAITING_FOR_RECEIVE---发送RECEIVE事件---//这里是步骤1订单的RECEIVE用户已收货,状态机反馈信息:{order=订单号:1724379861, 订单状态:DONE, id=fb9e4c14-2e6d-3cea-7fe0-6b1db20152e0, timestamp=1724379980224}用户已收货,状态机反馈信息:{order=订单号:1724379861, 订单状态:DONE, id=fb9e4c14-2e6d-3cea-7fe0-6b1db20152e0, timestamp=1724379980224}事件是否发送成功:true,当前状态:DONE---发送PAY事件---//这里是步骤3订单的PAY事件是否发送成功:false,当前状态:DONE可以看到,当上述步骤3 中创建的订单执行 PAY 事件时,结果是 FALSE,因为状态已经是 DONE,也就是说前一个订单的结束对当前订单产生了影响,此时 mongodb 中的数据截图如下:聊一聊 Spring StateMachine 的基本概念和实践image-20240823103840593这里有个比较明显的是,当前状态机的 id 是orderStateMachine,并非是前面看到的 订单号 id。所以就解释了为什么前后两个订单会产生影响了。细心的读者可能会发现,在配置类中,笔者指定了machineIdconfig.withConfiguration().machineId("orderStateMachine").autoStartup(true);那去掉之后会怎么样呢?会抛出 NPEimage-20240823104154720这个异常的原因是状态机 ID 是 null。这其实是个悖论,如果指定machineId,那么持久化会根据machineId作为查询 key ,导致多个订单状态机共享一个持久化状态机,从而相互影响;如果不指定machineId则会抛出空指针异常。笔者目前还有没找到比较合适的解决思路,如果有读者有不同的想法,敬请不吝赐教。A question about persistence use关于 StateMachineFactory 和 StateMachineModelFactory这两个 Factory 也是笔者在尝试解决上述问题时捎带看的,本质是期望能够通过 StateMachineFactory 来为每个订单创建一个新的状态机实例,从而解决前面提到的共享同一个状态机的问题;但是问题在于 StateMachineFactory 确实会为每个请求创建新的状态机,但是它并不能有效的和持久化机制协同起来工作。下面是具体原因。StateMachinemachine=this.stateMachineFactory.getStateMachine(String.valueOf(orderId));上面这段代码是通过 stateMachineFactory 来创建 StateMachine 的,按照常规的思路,在 getStateMachine 的方法实现中,理论上是需要支持从外部存储中获取 StateMachine 的,官方文档也确实是这样描述的;但是笔者通过简单的测试之后的理解是,这里的从外部存储中获取 StateMachine并非是持久化后的恢复,而是外部储存中提供了原始的 stateMachineModel,使得可以通过 stateMachineModel 来构建一个新的 StateMachine。下面的这段异常堆栈即是使用StateMachineFactory + RepositoryStateMachineModelFactory后测试得到的,因为笔者没有在存储库中提供任何 模型和转换的定义。org.springframework.statemachine.config.model.MalformedConfigurationException:Musthaveatleastonetransitionatorg.springframework.statemachine.config.model.verifier.BaseStructureVerifier.verify(BaseStructureVerifier.java:43)atorg.springframework.statemachine.config.model.verifier.CompositeStateMachineModelVerifier.verify(CompositeStateMachineModelVerifier.java:43)atorg.springframework.statemachine.config.AbstractStateMachineFactory.getStateMachine(AbstractStateMachineFactory.java:174)atorg.springframework.statemachine.config.AbstractStateMachineFactory.getStateMachine(AbstractStateMachineFactory.java:149)原理概述最后一个小节,笔者还是来剖析一下状态机的基本原理。总的来说是:Spring 状态机的基本原理是通过状态、事件和转换来管理对象的状态流转。状态机定义了对象的可能状态(State)及其之间的转换(Transition)。事件(Event)触发状态间的转换,并可能执行特定动作(Action)。状态机由状态(State)、事件(Event)、动作(Action)、守护(Guard)等组成,配置完成后,状态机根据输入事件变更状态。关于源码这块,因为 3.x 版本整体代码通过 Reactor 进行了重构,整体的代码可读性和 debug 上相比来说比较不友好,所以推荐有意向的读者可以基于 2.5.x 版本进行阅读分析和 debug;因篇幅问题,具体的源码分析和梳理笔者将单独用一篇文章来阐述。参考聊一聊 VirtualThread-虚拟线程【初识】-JUC·Executor框架【初识】-JUC·ThreadPoolExecutor 线程池Java Fork/Join 框架JUC线程池: Fork/Join框架详解
|
|