全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-09-05_聊一聊 Spring StateMachine 的代码和原理

您的位置:首页 >> 新闻 >> 行业资讯

聊一聊 Spring StateMachine 的代码和原理 点击关注公众号,“技术干货”及时达!?本文为稀土掘金技术社区首发签约文章 ?在这篇文章 聊一聊 Spring StateMachine 的基本概念和实践 中,我介绍了 Spring StateMachine 的基本概念和使用,并且通过一个案例对守卫、行动、持久化等特性进行了具体的演示。本篇是姊妹篇,是在实践的基础上进一步探索 Spring StateMachine 内部工作原理的产出。 StateMachine 类的体系结构状态机的具体实现类是包括 ObjectStateMachine 和 DistributedStateMachine,本篇主要围绕单机版本,DistributedStateMachine 的处理方式主要是引入了 Zookeeper,其他差距不大。在下面的文章中,我将从状态机的启停、事件发生及内部状态流转以及状态机持久化几个角度来分析源码,希望可以结合前一篇的案例,给读者提供更加丰满的理解。下面是 StateMachine 的类结构体系: 在前一篇中也对 region 做了简单的概念介绍:Region 允许在一个状态机中包含多个并行运行的状态子机,意味着同一时间可以在不同的子状态机中处理多个独立的状态转换,提供了一种并行执行状态的机制,它实际上描述的是一种更加复杂的层次关系和状态机之间的并行关系;StateMachine 继承了 Region 接口,意味着每一个状态机都是一个 Region,Region 本身提供了 启、停、事件发送以及状态获取等基本的方法,StateMachine 在 Region 的基础上为通用有限状态机提供了一些新的api,主要是用于处理状态、异常处理以及状态机访问等基本操作。 ObjectStateMachine 整合了 Spring Bean 的生命周期和 Region 的生命周期,实现了状态机全生命周期托管到 Spring。 Spring StateMachine 的启动和停止Spring StateMachine 的启动和停止主要涉及到的是 start 和 stop 两个方法,这两个方法并非是继承自 Region 的实现,而是 org.springframework.context.Lifecycle 的 start 和 stop 的实现。 StateMachine#start当执行 this.stateMachine.start(); 时,实际上调用的是org.springframework.statemachine.support.LifecycleObjectSupport#start方法 @Override publicfinalvoidstart(){ this.lifecycleLock.lock(); try{ if(!this.running){ this.running=true; this.doStart(); //omitted.. } }finally{ this.lifecycleLock.unlock(); } } 这里通过继承 org.springframework.context.Lifecycle 接口实现了状态机生命周期和 Sring Bean 生命周期的绑定。start 方法的调用链路如下: ?stateMachine.start() - org.springframework.statemachine.support.LifecycleObjectSupport#start - org.springframework.statemachine.support.StateMachineObjectSupport#doStart - org.springframework.statemachine.support.LifecycleObjectSupport#doStart - org.springframework.statemachine.support.AbstractStateMachine#doStart ?这里我们主要分析 StateMachineObjectSupport 和 AbstractStateMachine 两类中的逻辑。 StateMachineObjectSupport#start@Override protectedvoiddoStart(){ //org.springframework.statemachine.support.LifecycleObjectSupport#doStart super.doStart(); if(!handlersInitialized){ try{ stateMachineHandlerCallHelper.setBeanFactory(getBeanFactory()); //为org.glmapper.techssm.configs.StateMachineEventConfig类中的注解生成一组Handler,具体见下图 stateMachineHandlerCallHelper.afterPropertiesSet(); }catch(Exceptione){ log.error("Unabletoinitializeannotationhandlers", }finally{ handlersInitialized=true; } } } 这里方法的主要作用就是为上篇中 org.glmapper.techssm.configs.StateMachineEventConfig 类中的如 @OnTransition 等注解生成一组 Handler,如下图所示: 通过 StateMachineHandler、StateMachineRuntimeProcessor 以及 StateMachineMethodInvokerHelper 等相关类及其子类实现当触发某类事件时的反射调用。 AbstractStateMachine#start@Override protectedvoiddoStart(){ //这里调用的是org.springframework.statemachine.support.StateMachineObjectSupport#doStart super.doStart(); //omitted...省略一些代码 //注册伪状态监听器 registerPseudoStateListener(); if(initialEnabled!=null!initialEnabled){ if(log.isDebugEnabled()){ log.debug("Initialdisableasked,disablinginitial"); } stateMachineExecutor.setInitialEnabled(false); }else{ stateMachineExecutor.setForwardedInitialEvent(forwardedInitialEvent); } //startfiresfirstexecutionwhichshouldexecuteinitialtransition stateMachineExecutor.start(); } 上个代码片段中有个有意思的概念:PseudoState;官方注释的描述是:伪状态(PseudoState)是一种抽象概念,包含状态机中不同类型的瞬态或顶点。伪状态通常用于将多个过渡连接成更复杂的状态转换路径。例如,将进入 fork 伪状态的转换与离开 fork 伪状态的一组转换结合起来,我们就得到了一个复合转换,它通向一组正交的目标状态。。这里我通过 idea 看了下 PseudoState 的类结构,发现还有不少这种伪状态。 Choice 是一种基于条件的分支状态。它根据某个条件或判断进行状态分支,选择一个合适的目标状态。这类似于编程中的条件判断语句。Fork 允许将状态机的执行分成多个并行路径。进入 Fork 状态后,状态机会同时进入多个目标状态,形成并行的状态执行路径。Join 与 Fork 相对应,它用于将多个并行执行路径合并到一个路径。只有所有并行路径都到达 Join 状态时,状态机才会继续前进。对于事件初始状态,比如前一篇文章中,我提供了订单的 4 种状态,下面是在执行 registerPseudoStateListener 方法时可以明确的看到运行期的状态信息: 初始状态是 UNPAID,因此它的 PseudoState 会默认增加一个 INITIAL; 对于选择类型的,则会增加一个 Choice 类型的 PseudoState (具体可以参考上一篇文章中的代码)。 除注册伪状态监听器之外,比较核心就是启动 StateMachineExecutor,具体逻辑在 org.springframework.statemachine.support.DefaultStateMachineExecutor#doStart。 @Override protectedvoiddoStart(){ //这里是org.springframework.statemachine.support.LifecycleObjectSupport#doStart空方法 super.doStart(); //开启触发器 startTriggers(); //执行 execute(); } 首先是 startTriggers 方法,Spring StateMachine有两种类型的 Trigger: EventTrigger:基于事件的触发器。当特定的事件被发送到状态机时,EventTrigger 会检查该事件是否与其关联。如果匹配,状态机会根据定义的转换进行状态变更。TimerTrigger:基于时间的触发器。它会在指定的时间条件满足后自动触发状态机的状态转换。这两个从触发方式上来说是一个外,一个内。最后是 execute 方法,这里实际上就是启动了一个单线程消费一个队列的逻辑。 try{ //用于标记是否处理了事件 booleaneventProcessed=false; //不断处理事件队列,直到队列为空。对于每个处理的事件,它将eventProcessed设置为true,并处理触发器队列和延迟事件列表 while(processEventQueue()){ eventProcessed=true; processTriggerQueue(); while(processDeferList()){ processTriggerQueue(); } } //如果没有事件被处理,任务处理触发器队列和延迟事件列表。 if(!eventProcessed){ processTriggerQueue(); while(processDeferList()){ processTriggerQueue(); } } //处理完队列后,任务检查是否有新任务被请求,如果有新任务被请求,它将再次安排处理事件队列。 if(requestTask.getAndSet(false)){ scheduleEventQueueProcessing(); } //任务通过设置任务引用为null来表示当前任务已经完成,并进行第二次尝试安排处理事件队列,以减少线程导致运行失败的风险。 taskRef.set(null); } 其中 processEventQueue 涉及到状态转换的逻辑,从上段代码以及下面的代码来看,都用到了 线程 + 阻塞队列的方式实现的一种类似于生产者消费者逻辑的代码。 privatebooleanprocessEventQueue(){ //省略代码 //从事件队列中获取并移除队列的头部元素。 MessagequeuedEvent=eventQueue.poll(); //获取当前状态 StateS,EcurrentState=stateMachine.getState(); if(queuedEvent!=null){ //判断是不是可以延迟 if((currentState!=nullcurrentState.shouldDefer(queuedEvent))){ log.info("Currentstate"+currentState+"deferredevent"+queuedEvent); queueDeferredEvent(queuedEvent); returntrue; } //执行transition,包括源、目标以及事件 for(TransitionS,Etransition:transitions){ StateS,Esource=transition.getSource(); TriggerS,Etrigger=transition.getTrigger(); if(StateMachineUtils.containsAtleastOne(source.getIds(),currentState.getIds())){ if(trigger!=nulltrigger.evaluate(newDefaultTriggerContextS,E(queuedEvent.getPayload()))){ //这里是将trigger和queuedEvent丢到triggerQueue中去等待消费 //triggerQueue的消费逻辑在processTriggerQueue方法中 queueTrigger(trigger,queuedEvent); returntrue; } } } returntrue; } returnfalse; } 上述的一些核心逻辑均在 DefaultStateMachineExecutor 和 AbstractStateMachine 两个类中;从启动的角度来看,状态机的启动就是将状态机本身和 Spring 绑定,然后启动状态机内部的任务执行器以及一些阻塞队列的初始化,并形成在运行期时能够以生产者消费者模型不断处理不同事件的过程。 Spring StateMachine 的事件发送事件发送的核心代码也是在 AbstractStateMachine 中,这里先看入口方法 privatebooleansendEventInternal(Messageevent){ //如果状态机异常,则通知不接受事件 if(hasStateMachineError()){ //TODO:shouldwethrowexception? notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED,event,null,getRelayStateMachine(),getState(),null)); returnfalse; } try{ //状态机拦截器进行拦截处理 event=getStateMachineInterceptors().preEvent(event,this); }catch(Exceptione){ log.info("Event"+event+"threwexceptionininterceptors,notacceptingevent"); notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED,event,null,getRelayStateMachine(),getState(),null)); returnfalse; } //如果状态机运行完或者不再运行中,则通知不接受事件 if(isComplete()||!isRunning()){ notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED,event,null,getRelayStateMachine(),getState(),null)); returnfalse; } booleanaccepted=acceptEvent(event); stateMachineExecutor.execute(); if(!accepted){ //如果状态机没有正常接收和处理事件,则通知不接受事件 notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED,event,null,getRelayStateMachine(),getState(),null)); } returnaccepted; } 这里主要看 acceptEvent 这个方法,其他的均为在一些非常规情况下发送的事件,事件类型均为 EVENT_NOT_ACCEPTED。在 acceptEvent 中,最核心的逻辑在于,会遍历状态机中的所有 transitions,每个 transition 都有一个源状态和一个触发器,并对当前的状态做一些必要的检查;这里也会涉及到 Guard 的校验,如果都通过,则当前转换事件会被丢到 stateMachineExecutor 的 eventQueue 中。 所以事件发送就是从外部发起一个触发条件,在提交到 stateMachineExecutor 和相关的队列之前做一些比较的校验。当事件被推送到 stateMachineExecutor 的相关队列之后,相关的逻辑就是上一个小节中启动处理的逻辑。 Spring StateMachine 的持久化关于持久化部分,可以分两块来看,一种是 StateMachinePersister,还有一种是 StateMachinePersist ,是的看起来很像,但是从提供的接口方法来看,还是完全不同的。 StateMachinePersist 体系 StateMachinePersister 体系 StateMachinePersister 提供的是 persist 和 restore 一对方法,主要是将 StateMachine 进行持久化和恢复。StateMachinePersist 提供的是 write 和 read 一对方法,他关注的是 StateMachineContext,而不是 StateMachine。实际上从源码角度,StateMachinePersister 的 persist 和 restore 方法会委托给 StateMachinePersist 的 write 和 read 方法。如 AbstractStateMachinePersister 中的逻辑 @Override publicfinalvoidpersist(StateMachineS,EstateMachine,TcontextObj)throwsException{ //使用stateMachinePersist.write stateMachinePersist.write(buildStateMachineContext(stateMachine),contextObj); } @Override publicfinalStateMachineS,Erestore(StateMachineS,EstateMachine,TcontextObj)throwsException{ //使用stateMachinePersist.read finalStateMachineContextS,Econtext=stateMachinePersist.read(contextObj); //省略其他代码... } 关于持久化部分,最后在来看下序列化的逻辑;Spring 状态机仅提供了 Kryo 一种序列化方式。具体实现类是 KryoStateMachineSerialisationService。它提供了 doEncode 和 doDecode 两个基本的方法,底层则是直接依赖 com.esotericsoftware.kryo.Kryo#writeObject 和 com.esotericsoftware.kryo.Kryo#writeObject 两个方法来支持具体的对象编解码工作。 总结本篇文章是 聊一聊 Spring StateMachine 的基本概念和实践(https://juejin.cn/post/7406144322856353843)的延续,从实践到源码分析;关于 Spring StateMachine 其核心代码不算复杂,重要的是需要先理解概念再理解状态机模型。本质上内部通过事件发布、订阅和线程池 + 阻塞队列实现了整个状态的流转、执行拦截、事件通知回调以及转换校验等。 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2025-07-05_ICCV 2025|降低扩散模型中的时空冗余,上交大EEdit实现免训练图像编辑加速 下一篇:2023-10-18_端侧AI推理,高效部署PyTorch模型:官方新工具开源,Meta已经用上了

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价