squirrel-foundation状态机的使用细节

squirrel-foundation状态机的使用细节

上一篇文章介绍了stateless4j、spring-statemachine以及squirrel-foundation三款状态机引擎的实现原理,以及我为何选择squirrel-foundation作为解决方案。本文主要介绍一下项目中如何使用squirrel-foundation的一些细节以及如何与spring进行集成。在阅读本文前,建议先阅读官方的使用手册

生命周期

状态机创建过程

  • StateMachine: StateMachine实例由StateMachineBuilder创建不被共享,对于使用annotation方式(或fluent api)定义的StateMachine,StateMachine实例即根据此定义创建,相应的action也由本实例执行,与spring的集成最终要的就是讲spring的bean实例注入给由builder创建的状态机实例;
  • StateMachineBuilder: 本质上是由StateMachineBuilderFactory创建的动态代理。被代理的StateMachineBuilder默认实现为StateMachineBuilderImpl,内部描述了状态机实例创建细节包括State、Event、Context类型信息、constructor等,同时也包含了StateMachine的一些全局共享资源包括StateConverter、EventConverter、MvelScriptManager等。StateMachineBuilder可被复用,使用中可被实现为singleton;
  • StateMachineBuilderFactory: 为StateMachineBuilder创建的动态代理实例;

事件处理过程

  • 状态正常迁移
    TransitionBegin--(exit->transition->entry)-->TransitionComplete-->TransitionEnd
  • 状态迁移异常
    TransitionBegin--(exit->transition->entry)-->TransitionException-->TransitionEnd
  • 状态迁移事件拒绝
    TransitionBegin-->TransitionDeclined-->TransitionEnd

spring集成

从statemachine的生命流程上可以看到,StateMachineBuilder可以单例方式由spring container管理,而stateMachine的instance的生命周期伴随着请求(或业务)。
从这两点出发,集成spring需要完成两件事:

  • (1).通过Spring创建StateMachineBuilder实例;
  • (2).业务函数中通过(1)的StateMachineBuilder实例创建StateMachine实例,并向StateMachine暴露SpringApplicationContext;

泛型参数+覆盖默认构造函数隐藏StateMachineBuilder创建细节,实现ApplicationContextAware接口,接受applicationContext注入,并注入给stateMachine实例。

public abstract class AbstractStateMachineEngine<T extends UntypedStateMachine> implements ApplicationContextAware {
    protected UntypedStateMachineBuilder stateMachineBuilder = null;
    @SuppressWarnings("unchecked")
    public AbstractStateMachineEngine() {
        //识别泛型参数
        Class<T> genericType = (Class<T>)GenericTypeResolver.resolveTypeArgument(getClass(),
            AbstractStateMachineEngine.class);
        stateMachineBuilder = StateMachineBuilderFactory.create(genericType, ApplicationContext.class);
    }
    //注入applicationContext,并在创建StateMachine实例时注入
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    //delegate fire
    public void fire(int rmaId, State initialState, Trigger trigger, StateMachineContext context) {
        T stateMachine = stateMachineBuilder.newUntypedStateMachine(
                            initialState
                            //暂时开启debug进行日志trace
                            StateMachineConfiguration.create().enableDebugMode(true).enableAutoStart(true),
                            //注入applicationContext
                            applicationContext);
        stateMachine.fire(trigger, context);
    }
    ...
}
@Service
class DiscountRefundStateMachineEngine extends AbstractStateMachineEngine<DiscountRefundStateMachine> {
}
@Service
public class ReturnGoodsStateMachineEngine extends AbstractStateMachineEngine<ReturnGoodsStateMachine> {
}

StateMachine定义,接受SpringContext注入

@StateMachineParameters(stateType = State.class, eventType = Trigger.class,
    //StateMachineContext 自定义上下文,用来传递数据
    contextType = StateMachineContext.class)
@States({
    @State(name = "PENDING", initialState = true),
    @State(name = "CONFIRMING"),
    @State(name = "REJECTED"),
    @State(name = "REFUND_APPROVING"),
    @State(name = "REFUND_APPROVED"),
    @State(name = "REFUND_FINISHED")
})
@Transitions({
    @Transit(from = "PENDING", to = "CONFIRMING", on = "APPLY_CONFIRM",
        callMethod = "doSomething"),
    @Transit(from = "CONFIRMING", to = "REJECTED", on = "REJECT"),
    @Transit(from = "CONFIRMING", to = "REFUND_APPROVING", on = "APPLY_APPROVED"),
    @Transit(from = "REFUND_APPROVING", to = "REFUND_APPROVED", on = "REFUND_APPROVED"),
    @Transit(from = "REFUND_APPROVED", to = "REFUND_FINISHED", on = "REFUND_FINISH_CONFIRM")
})
public class DiscountRefundStateMachine extends AbstractUntypedStateMachine {
    protected ApplicationContext applicationContext;
    //定义构造函数接受ApplicationContext注入([参看New State Machine Instance](http://hekailiang.github.io/squirrel/))
    public DiscountRefundStateMachine(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    public void doSomething(State fromState, State toState, Trigger event,
                         StateMachineContext stateMachineContext) {
         DemoBean demoBean = this.applicationContext.get("demoBean");
         //do something
    }
    ...
}

状态持久化

从StateMachine的事件响应流程中可以看到,TransitionBegin--(exit->transition->entry)-->TransitionComplete-->TransitionEnd,在TransitionComplete发生一个状态已从source迁移到了target状态,所以我选择了在这个时间点进行了状态的持久化(没有选择TransitionEnd做持久化,因为某些场景在持久化完成后还会存在一些外部动作的触发,例如通知第三方系统当前状态已完成变更)。

public class DiscountRefundStateMachine extends AbstractUntypedStateMachine {
    ..
    @Override
    protected void afterTransitionCompleted(Object fromState, Object toState, Object event, Object context) {
        if (context instanceof StateMachineContext && toState instanceof State) {
            StateMachineContext stateMachineContext = (StateMachineContext)context;
            //从上下文中获取需要持久化的数据,例如订单ID等
            Rma rma = stateMachineContext.get(MessageKeyEnum.RMA);
            //持久化
            rma.setStatus((State)toState);
            this.applicationContext.get("rmaRepository").updateRma(rma);
        } else {
            throw new Exception("type not support, context expect " + StateMachineContext.class.getSimpleName() + ", actually "
                    + context.getClass().getSimpleName() + ", state expect " + State.class.getSimpleName()
                    + ", actually "
                    + toState.getClass().getSimpleName());
        }
    }
}

分布式锁+事务

由于StateMachine实例不是由Spring容器创建,所以这个过程中无法通过注解方式开启事务(Spring没有机会去创建事务代理),我采用了编程式事务,在AbstractStateMachineEngine的fire函数中隐式的实现。
AbstractStateMachineEngine#fire

public abstract class AbstractStateMachineEngine<T extends UntypedStateMachine> implements ApplicationContextAware {
    ...
    public void fire(int rmaId, State initialState, Trigger trigger, StateMachineContext context) {
        JedisLock jedisLock = jedisLockFactory.buildLock(rmaId);
        //争用分布式锁
        if (jedisLock.tryLock()) {
            try {
                T stateMachine = stateMachineBuilder.newUntypedStateMachine(
                                    initialState
                                    //暂时开启debug进行日志trace
                                    StateMachineConfiguration.create().enableDebugMode(true).enableAutoStart(true),
                                    //注入applicationContext
                                    applicationContext);
                DataSourceTransactionManager transactionManager = applicationContext.get("transactionManager")
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
                TransactionStatus status = transactionManager.getTransaction(def);
                try {
                    stateMachine.fire(trigger, context)
                    transactionManager.commit(status);
                } catch (Exception ex) {
                    transactionManager.rollback(status);
                    throw ex;
                }
            } finally {
                jedisLock.release();
            }
        }
        ...
    }
}

使用graphviz生成状态拓扑图

squirrel statemachine提供了DotVisitor、SCXMLVisitor两种实现方式用于生成状态机描述文件,项目里我选择了graphviz用来做状态拓扑
graphviz gui工具下载
PS:由于squirrel默认的DotVisitorImpl对带中文描述属性的State/Event枚举不友好,我在原有代码上做了一些调整,有类似需求的可以看这里

更多文章请访问我的博客
转载请注明出处

时间: 2024-10-11 22:06:37

squirrel-foundation状态机的使用细节的相关文章

《游戏编程模式》一7.8 并发状态机

7.8 并发状态机 我们决定给我们的主角添加持枪功能.当她持枪的时候,她仍然可以:跑.跳和躲避等.但是,她也需要能够在这些状态过程中开火. 如果你执着于传统的有限状态机,那我们可能需要把之前的状态加倍.对于每一个已经存在的状态,我们需要定义另一个状态,它做的事情也差不多,不过就是多了持枪的操作.比如站立状态和站立开火状态,跳跃状态和跳跃开火状态等. 如果我们添加更多的武器种类,那么这个状态数量将会急剧增加.而且不仅仅是增加了大量的状态类实例,它还会增加大量的冗余,实际上带不带枪的状态仅有是否包含

用状态机来设计cell动画

用状态机来设计cell动画   前言 一个cell可能有好几种状态,比方说选中状态与未选中状态,以及失效状态等等状态,我们将这些不同的情形抽象成一个个状态机,用状态机切换逻辑来设计复杂的动画效果,达到简化设计的目的,大大增加程序可维护性. * 大家需要注意一点,cell因为是可以复用的控件,因为存在复用,所以里面存在较为恶心的复用逻辑,设计动画接口时是需要注意这些小细节的.(亲手写过的人一定会深有体会)   效果   源码 https://github.com/YouXianMing/CellS

状态机引擎选型

状态机引擎选型 概念 有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件.在电商场景(订单.物流.售后).社交(IM消息投递).分布式集群管理(分布式计算平台任务编排)等场景都有大规模的使用. 状态机的要素 状态机可归纳为4个要素,即现态.条件.动作.次态."现态"和"条件"是因,"动作"和"次态"是果.详解如下: ①现态:是指当前所处的状态. ②条

SharePoint状态机工作流解决方案(一):为什么要用状态机

以前一直是作 Windows Workflow Foundation 的工作流平台,对 WF 比较熟悉,开发的工作流平台满 足了公司实施的各种项目的工作流应用的需求. 最近作了一个 SharePoint 文档库项目,里面的审批流程,涉及到 SharePoint 工作流:一直都听说 SharePoint 下没有成熟的工作流解决方案,但接触了以后发现,SharePoint 是一个非常好的工作流平台 :虽然在实际应用中还有一些设计和技术上的问题需要解决,可这些问题解决以后,SharePoint 工作流

使用Visual Studio 2010在WPF中构建数据驱动的大纲/细节业务表

概述 在本实验中,您将了解如何使用 Visual Studio 2010 工具在 WPF 4.0 中创建和自定义大纲/细节业务表. 目标 完成此实验后,您将学会: 如何在 WPF 项目中使用"Data Sources"窗口创建初始支架,以绑定您应用程序的数据 如何使用"Data Sources"窗口在现有 WPF 控件中"绘制"数据绑定 如何使用"Data Sources"窗口创建大纲/细节支架 如何自定义"Data

Visual Studio DSL 入门 11---为状态机设计器添加规则

上一节我们在设计器的显示方面进行了完善,在这一节,我们将深入状态机设计器的一些逻辑细节,给我 们的设计器添加逻辑规则.为生成代码做好准备. 在开始之前,我们先看一下Transition的几个属性之间的关系: 1.编辑Event,Condition,Action属性时,Label属性能够自动计算显示,计算逻辑为Event [Condition] / Action 2.当修改属性Label时,Event,Condition,Action的值也能够对应自动更新. 我们使用Vs.net Dsl的规则来实

iOS 8应用构建细节挖掘之应用启动流程

 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 - 本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作.   24K 标题党! 不过内容绝对够细节而全面,仅针对启动流程这一小块块哟!   iOS 应用启动流程,这个话题早在09年就非常熟悉,然而时隔多年,不知是否还熟悉,尤其 StoryBoard 的引入,那么下面就一起来看看吧,如果确实

状态机的两种写法

有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软 件上称为FMM--有限消息机).它把复杂的控制逻辑分解成有限个稳定状态,在每个状态 上判断事件,变连续处理为离散数字处理,符合计算机的工作特点.同时,因为有限状 态机具有有限个状态,所以可以在实际的工程上实现.但这并不意味着其只能进行有限 次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的 事务.     有限状态机的工作原理如图1所示,发生事件(event)后,根据当前状态(cur_st

apache oro使用注意细节(并发问题)

背景   距离上一篇文章已经有4个多月了,这4个多月一直在忙着做一个数据库同步产品的代码研发和测试,现在基本运行稳定. 本文主要介绍一下,当时使用apache oro包进行正则过滤时,使用时出现的一个并发问题,排查了好几天才找到原因.希望大家使用时引以为戒,望周知.   过程 简单的描述下,我使用apache oro的场景: 进行数据库同步时,我们会根据定义的表名进行匹配,从binlog的数据流中提取出我们关心的表,然后进行解析,压缩,传输,写入目标库等一系列动作.  然而在线下测试环境中,冒出