Springboot整合squirrel-foundation状态机

目录

一. 快速入门

1 . maven

2 . 快速开始

3 . Fluent Api 

4 . 状态机四要素

5 . 状态机构建器

6 . 状态机转换操作(代码配置方式)

7 . 状态机转换操作(注解声明方式)

8 . 上下文不敏感状态机

二 : 使用注意事项

P1 :  异常 :  StateMachineBuilderImpl cannot find Initial state 'B' in state machine

P2 :  转换异常 : 


上一篇文章介绍了 状态机介绍以及常见种类对比  。本篇主要介绍一下项目中如何使用squirrel-foundation的一些细节以及如何与spring进行集成的流程。

状态机(有限状态自动机 FSM)介绍_黄嚯嚯-CSDN博客状态机概念, 为什么要使用状态机https://blog.csdn.net/qq_42543063/article/details/119111658 阅读过程中如有介绍不全的位置  , 也可以留言 , 或者参考官方文档自行分析   squirrel-foundation | Squirrel State Machinesquirrel-foundation provided an easy use, type safe and highly extensible state machine implementation for Java.http://hekailiang.github.io/squirrel/

一. 快速入门

1 . maven

squirrel-foundation 的最新包 已经部署到了maven的中央仓库,所以只需要在pom.xml中添加如下依赖即可。

目前最新版本 

<dependency>
	<groupId>org.squirrelframework</groupId>
  	<artifactId>squirrel-foundation</artifactId>
  	<version>0.3.8</version>
</dependency>

该状态机引擎的maven有可能会引起一下jar 冲突 , 导致项目启动Or 执行异常 

        <!--状态机引擎-->
        <dependency>
            <groupId>org.squirrelframework</groupId>
            <artifactId>squirrel-foundation</artifactId>
            <version>0.3.8</version>
            <!-- 可能会出现冲的的相关jar 如果项目中没有引入一下几类可忽略--> 
            <exclusions>
                <exclusion>
                    <artifactId>log4j</artifactId>
                    <groupId>log4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>guava</artifactId>
                    <groupId>com.google.guava</groupId>
                </exclusion>
            </exclusions>
        </dependency>

2 . 快速开始

squirrel-foundation官方文档提供了一个小demo , 相对来说比较简洁, 非常适合初学者 . , 一下这个quickStartSample 是可以直接运行的 , 可以先动手尝试 , 细节点请继续往下看

public class QuickStartSample {
    
    // 1. 定义一个事件枚举类
    enum FSMEvent {
        ToA, ToB, ToC, ToD
    }
    // 2. 定义一个状态机本体
    @StateMachineParameters(stateType=String.class, eventType=FSMEvent.class, contextType=Integer.class)
    static class StateMachineSample extends AbstractStateMachine<MyStateMachine , FSMState , FSMEvent , Integer> {
        protected void fromAToB(String from, String to, FSMEvent event, Integer context) {
            System.out.println("Transition from '"+from+"' to '"+to+"' on event '"+event+
                "' with context '"+context+"'.");
        }
        protected void ontoB(String from, String to, FSMEvent event, Integer context) {
            System.out.println("Entry State \'"+to+"\'.");
        }
    }
    public static void main(String[] args) {
        // 3. 构建转换builder
                StateMachineBuilder<MyStateMachine, FSMState, FSMEvent, Integer> builder
                = StateMachineBuilderFactory.create(MyStateMachine.class, FSMState.class,
                FSMEvent.class, Integer.class);
        builder.externalTransition().from("A").to("B").on(FSMEvent.ToB).callMethod("fromAToB");
        builder.onEntry("B").callMethod("ontoB");
        
        // 4. 使用状态机
        MyStateMachine fsm = builder.newStateMachine("A");
        // 5 . 驱动状态
        fsm.fire(FSMEvent.ToB, 10);
        System.out.println("Current state is "+fsm.getCurrentState());
    }
}

首先 squirrel-foundation 支持 fluent API 和声明方式来声明状态机,并且还允许用户以直接的方式定义操作方法。

但是 , 官方文档并没有介绍 fluent API 这种编码方式 

3 . Fluent Api 

quickStartSample 中体现在

builder.externalTransition().from("A").to("B").on(FSMEvent.ToB).callMethod("fromAToB");

在编写软件库的时候,我们有两种选择。一种是提供Command-Query API,另一种是Fluent Interfaces。比如Mockito的API when(mockedList.get(anyInt())).thenReturn("element")就是一种典型连贯接口的用法。

Fluent的这种连贯性带来的可读性和可理解的提升,其本质不仅仅是在提供API,更是一种领域语言,是一种Internal DSL。

比如Mockito的API when(mockedList.get(anyInt())).thenReturn("element")就非常适合用Fluent的形式,实际上,它也是单元测试这个特定领域的DSL。

这里需要注意的是,连贯接口不仅仅可以提供类似于method chaining和builder模式的方法级联调用,比如OkHttpClient中的Builder

他更重要的作用是,限定方法调用的顺序。比如,在构建状态机的时候,我们只有在调用了from方法后,才能调用to方法,Builder模式没有这个功能。

OkHttpClient.Builder builder=new OkHttpClient.Builder();
        OkHttpClient okHttpClient=builder
                .readTimeout(5*1000, TimeUnit.SECONDS)
                .writeTimeout(5*1000, TimeUnit.SECONDS)
                .connectTimeout(5*1000, TimeUnit.SECONDS)
                .build();

4 . 状态机四要素

QuickStartSample 中体现在如下 

@StateMachineParameters(stateType=String.class, eventType=FSMEvent.class, contextType=Integer.class)
    static class StateMachineSample extends AbstractStateMachine<MyStateMachine , FSMState , FSMEvent , Integer>

StateMachine接口采用四个泛型类型参数。StateMachine<T, S, E, C> ,

ps : 要是想看这几个状态的可以向上找下  AbstractStateMachine 关系如下 👇 

MyStateMachine extends AbstractUntypedStateMachine

AbstractUntypedStateMachine extends AbstractStateMachine

  • 代表已实现状态机的类型 ,, 也就是状态机本体。
  • 代表实现状态的类型 。
  • 代表已实现事件的类型。
  • 代表已实现的外部上下文的类型。

5 . 状态机构建器

QuickStartSample 中体现在如下 

StateMachineBuilder<MyStateMachine, MyState, MyEvent, MyContext> builder =
      StateMachineBuilderFactory.create(MyStateMachine.class, MyState.class, MyEvent.class, MyContext.class);		

为了创建状态机,用户需要先创建状态机构建器。 

  • 状态机构建器用于生成状态机的相关状态转换定义。StateMachineBuilder 可以由 StateMachineBuilderFactory 创建 。ps: 后续博主会更新相关使用 , 届时将自定义 StateMachineBuilderFactory
  • StateMachineBuilder 由用于构建状态之间的转换的 *TransitionBuilder (InternalTransitionBuilder / LocalTransitionBuilder / ExternalTransitionBuilder) 和用于构建进入或退出状态期间动作的 EntryExitActionBuilder 。
  • 内部状态是在转换创建或状态操作期间隐式构建的。
  • 由同一个状态机构建器创建的所有状态机实例共享相同的内存。
  • 状态机构建器以惰性方式生成状态机定义。当构建器创建第一个状态机实例时,将生成状态机定义,首次比较消耗时间。但是在生成状态机定义之后,接下来的状态机实例创建会快很多。一般来说,状态机构建器应该尽可能地重用。

6 . 状态机转换操作(代码配置方式)

  • 一个状态转换至另一个状态
        // 事件 ToB 触发条件转换由 A -> B  , 调用执行方法  formToB  以此类推
        builder.externalTransition().from(FSMState.A).to(FSMState.B).on(FSMEvent.GoToB).callMethod("fromAGoToB");
        //使用状态机
        MyStateMachine machine = builder.newStateMachine(FSMState.A);
        machine.fire(FSMEvent.GoToB, 22);
  •   n个状态中任意一个状态转换至另一个状态 ( 反之(调转一下fromAmong ) 一个状态转换至n个状态中任意一个状态 
        // 事件 GoToD 触发条件转换由 A Or B Or C  ->  D  , 调用执行方法  transitFromAnyToDOnGoToD  以此类推
        builder.externalTransitions().fromAmong(FSMState.A,FSMState.B,FSMState.C).to(FSMState.D).on(FSMEvent.GoToD).callMethod("transitFromAnyToDOnGoToD");
        //使用状态机 正常状态转换B
        MyStateMachine machine = builder.newStateMachine(FSMState.B);
        machine.fire(FSMEvent.GoToD, 77);
  •  多种状态任意转换  , 多种事件  之间转换也可以写成如下这种方式 配置

注意事项详见文章末尾  P1 !!!!!!!!!!!!!!!!!

        // 构建状态机转换Builder
        StateMachineBuilder<MyStateMachine, FSMState, FSMEvent, Integer> builder
                = StateMachineBuilderFactory.create(MyStateMachine.class, FSMState.class,
                FSMEvent.class, Integer.class);

        // 定义转换事件
        builder.externalTransition().from(FSMState.A).to(FSMState.B).on(FSMEvent.GoToB).callMethod("fromAGoToB");
        builder.externalTransition().from(FSMState.A).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromAGoToC");
        builder.externalTransition().from(FSMState.B).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromBGoToC");
        // A -> 被任意事件触发 -> 到任意状态 调用ssss 函数
        builder.transit().from(FSMState.A).toAny().onAny().callMethod("ssss");
        // 任意事件触发 -> 任意状态 -> B 调用函数
        builder.transit().fromAny().to(FSMState.C).onAny().callMethod("aaaaa");
        // 当 , 当前状态时 B 时候 , 触发执行方法  ontoB
        builder.onEntry(FSMState.B).callMethod("ontoB");
        builder.onEntry(FSMState.C).callMethod("ontoC");
        //使用状态机 正常状态转换
        MyStateMachine machine = builder.newStateMachine(FSMState.B);
        machine.fire(FSMEvent.GoToC, 22);
        System.out.println("当前状态是 : " + machine.getCurrentState());
  • 当状态进入某个状态 ( 若要触发onEntry() , 就得配置相对应的进入操作 , 例如要进入 状态 B 时候有动作 , 就得构建状态装换动作 , 来完成
         // 事件 ToB 触发条件转换由 A -> B  , 调用执行方法  formToB  以此类推
        builder.externalTransition().from(FSMState.A).to(FSMState.B).on(FSMEvent.GoToB).callMethod("fromAGoToB");
        builder.onEntry(FSMState.B).callMethod("ontoB");
        //使用状态机
        MyStateMachine machine = builder.newStateMachine(FSMState.A);
        machine.fire(FSMEvent.GoToB, 22);
  • 当状态退出某个状态( 若要触发 onExit() , 注意事项等同于 触发onEntry()
        // 事件 GoToD 触发条件转换由 A Or B Or C  ->  D  , 调用执行方法  transitFromAnyToDOnGoToD  以此类推
        builder.externalTransitions().fromAmong(FSMState.A,FSMState.B,FSMState.C).to(FSMState.D).on(FSMEvent.GoToD).callMethod("transitFromAnyToDOnGoToD");
        builder.onExit(FSMState.A).callMethod("exit");
        //使用状态机 正常状态转换B
        MyStateMachine machine = builder.newStateMachine(FSMState.A);
        machine.fire(FSMEvent.GoToD, 77);

7 . 状态机转换操作(注解声明方式)


// 定义状态机
@States({
        @State(name = "A" , entryCallMethod = "ontoA" , exitCallMethod = "exitA")
})
@Transitions({
        @Transit(from = "A" , to = "B" , on = "GoToB" , callMethod = "fromAGoToB"),
        @Transit(from = "A" , to = "C" , on = "GoToC" , callMethod = "fromAGoToB")
})
@SuppressWarnings("all")
@StateMachineParameters(stateType = FSMState.class, eventType = FSMEvent.class, contextType = Integer.class)
public class MyAnnotationStateMachine extends AbstractStateMachine<MyAnnotationStateMachine, FSMState , FSMEvent , Integer> {
  • 配置状态进入或退出

entryCallMethod : 是当状态进入时的操作  ,   exitCallMethod = 是当状态推出时的操作

@States({
        @State(name = "A" , entryCallMethod = "ontoA" , exitCallMethod = "exitA")
})
  • 配置状态转换操作

状态因为触发事件 (on)  从  (from)  到  ( to)   ,  调用方法 (callMethod)     

@Transitions({
        @Transit(from = "A" , to = "B" , on = "GoToB" , callMethod = "fromAGoToB"),
        @Transit(from = "A" , to = "C" , on = "GoToC" , callMethod = "fromAGoToB")
})
  • 多状态任意转换

注意事项详见文章末尾  P1 !!!!!!!!!!!!!!!!!

// 最后一条配置的是  GoToC 事件触发的任意状态转换至C都会触发 fromAnyToC
@Transitions({
        @Transit(from = "A", to = "B", on = "GoToB", callMethod = "fromAGoToB"),
        @Transit(from = "A", to = "C", on = "GoToC", callMethod = "fromAGoToC"),
        @Transit(from = "B", to = "C", on = "GoToC", callMethod = "fromAGoToC"),
        @Transit(from = "*", to = "C", on = "GoToC", callMethod = "fromAnyToC")
})

8 . 上下文不敏感状态机

有时状态转换不关心上下文,这意味着转换大多仅由事件决定。对于这种情况,用户可以使用上下文不敏感状态机来简化方法调用参数。
声明上下文不敏感状态机非常简单。用户只需要在状态机实现类上添加注解@ContextInsensitive。之后,可以忽略转换方法参数列表中的上下文参数。例如

@ContextInsensitive
  public class ATMStateMachine extends AbstractStateMachine<ATMStateMachine, ATMState, String, Void> {
      // 这里不再需要添加上下文参数
      public void transitFromIdleToLoadingOnConnected(ATMState from, ATMState to, String event) {
      	...
  	}
  	public void entryLoading(ATMState from, ATMState to, String event) {
      	...
  	}
}

二 : 使用注意事项

P1 :  异常 :  StateMachineBuilderImpl cannot find Initial state 'B' in state machine

1 . 这是最初模样 ,  是正常可执行的.

    private static void test1(){
        // 构建状态机转换Builder
        StateMachineBuilder<MyStateMachine, FSMState, FSMEvent, Integer> builder
                = StateMachineBuilderFactory.create(MyStateMachine.class, FSMState.class,
                FSMEvent.class, Integer.class);

        // 事件 ToB 触发条件转换由 A -> B  , 调用执行方法  formToB  以此类推
        builder.externalTransition().from(FSMState.A).to(FSMState.B).on(FSMEvent.GoToB).callMethod("fromAGoToB");
        builder.externalTransition().from(FSMState.A).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromAGoToC");
        builder.externalTransition().from(FSMState.B).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromBGoToC");
        builder.transit().fromAny().to(FSMState.C).onAny().callMethod("aaaaa");
        builder.transit().from(FSMState.A).toAny().onAny().callMethod("ssss");
        // 当 , 当前状态时 B 时候 , 触发执行方法  ontoB
        builder.onEntry(FSMState.B).callMethod("ontoB");
        builder.onEntry(FSMState.C).callMethod("ontoC");

        //使用状态机 正常状态转换
        MyStateMachine machine = builder.newStateMachine(FSMState.B);
        machine.fire(FSMEvent.GoToC, 22);
        System.out.println("当前状态是 : " + machine.getCurrentState());

    }

2 . 接下来稍作修改 注释掉 一部分状态转换的定义 , 可进入某个装态的定义 ,  只保留 两个任意状态装换的定义  

    private static void test1(){
        // 构建状态机转换Builder
        StateMachineBuilder<MyStateMachine, FSMState, FSMEvent, Integer> builder
                = StateMachineBuilderFactory.create(MyStateMachine.class, FSMState.class,
                FSMEvent.class, Integer.class);
        // 事件 ToB 触发条件转换由 A -> B  , 调用执行方法  formToB  以此类推
        //builder.externalTransition().from(FSMState.A).to(FSMState.B).on(FSMEvent.GoToB).callMethod("fromAGoToB");
        //builder.externalTransition().from(FSMState.A).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromAGoToC");
        //builder.externalTransition().from(FSMState.B).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromBGoToC");
        builder.transit().fromAny().to(FSMState.C).onAny().callMethod("aaaaa");
        builder.transit().from(FSMState.A).toAny().onAny().callMethod("ssss");
        // 当 , 当前状态时 B 时候 , 触发执行方法  ontoB
        //builder.onEntry(FSMState.B).callMethod("ontoB");
        //builder.onEntry(FSMState.C).callMethod("ontoC");

        //使用状态机 正常状态转换
        MyStateMachine machine = builder.newStateMachine(FSMState.B);
        machine.fire(FSMEvent.GoToC, 22);
        System.out.println("当前状态是 : " + machine.getCurrentState());
    }

3 . 在执行之后就直接抛了如下异常  , 说是找不到A这个状态 没法初始化状态机 

Exception in thread "main" java.lang.IllegalArgumentException: class org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl cannot find Initial state 'B' in state machine.
	at org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl.newStateMachine(StateMachineBuilderImpl.java:708)
	at org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl.newStateMachine(StateMachineBuilderImpl.java:700)
	at org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl.newStateMachine(StateMachineBuilderImpl.java:695)
	at com.ko.assy.squirrel.statemachine.simple.QuickStartSample.test1(QuickStartSample.java:36)
	at com.ko.assy.squirrel.statemachine.simple.QuickStartSample.main(QuickStartSample.java:16)

4 . 为了找出问题所在, 逐步放开 在 p2 上面注释的代码  , 做修改如下  , 

        // 注意此处放开了注释  , 但只是放开了有关 进入状态C 的定义  ,  并没有放开 B 的定义 , 结果执行仍旧报错 
        builder.onEntry(FSMState.C).callMethod("ontoC");

接下来放开 //builder.onEntry(FSMState.B).callMethod("ontoB");  ,  在这里面定义了有关进入B 的相关操作  , 执行如下

//使用状态机 正常状态转换   B -> C   按以上的  builder.transit().fromAny().to(FSMState.C).onAny().callMethod("aaaaa"); 这一句理解的话 , 应该会触发的 , 可实际并没有触发
MyStateMachine machine = builder.newStateMachine(FSMState.B);
machine.fire(FSMEvent.GoToC, 22);

结论   ::::   原因是因为只定义了 进入B  和 进入 C ,  虽然有了这个定义,  但是仍旧缺失  初始值 是 B ,  触发事件是 GotoC 的定义  

所以只需要放开上面的这一句即可 , 有操作 就得有定义 !!

builder.externalTransition().from(FSMState.B).to(FSMState.C).on(FSMEvent.GoToC).callMethod("fromBGoToC");

P2转换异常 : 

当状态转换过程中发生异常时,执行的动作列表将被中止,状态机将进入错误状态,这意味着状态机实例不能再处理事件。如果用户继续向状态机实例触发事件,则会抛出 IllegalStateException。
过渡阶段发生的所有异常,包括动作执行和外部侦听器调用,都将被包装到 TransitionException(未检查异常)中。目前,默认的异常处理策略简单粗暴,只是继续抛出异常,参见 AbstractStateMachine.afterTransitionCausedException 方法。

 protected void afterTransitionCausedException(...) { throw e; }

如果可以从该异常中恢复状态机,用户可以扩展 afterTransitionCausedException 方法,并在该方法中添加相应的恢复逻辑。最后不要忘记将状态机状态设置回正常。例如

  @Override
  protected void afterTransitionCausedException(Object fromState, Object toState, Object event, Object context) {
      Throwable targeException = getLastException().getTargetException();
      // recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'
      if(targeException instanceof IllegalArgumentException && 
              fromState.equals("A") && toState.equals("B") && event.equals("ToB")) {
          // do some error clean up job here
          // ...
          // after recovered from this exception, reset the state machine status back to normal
          setStatus(StateMachineStatus.IDLE);
      } else if(...) {
          // recover from other exception ...
      } else {
          super.afterTransitionCausedException(fromState, toState, event, context);
      }
  }

 PS:   以上是入门级的 快速开始demo  ,  后续博主会更新自定义注册工厂  以及自定义可扩展的功能 

Springboot整合squirrel-foundation状态机自定义可扩展_黄嚯嚯-CSDN博客Springboot整合squirrel-foundation状态机https://blog.csdn.net/qq_42543063/article/details/120995893

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值