目录
P1 : 异常 : StateMachineBuilderImpl cannot find Initial state 'B' in state machine
上一篇文章介绍了 状态机介绍以及常见种类对比 。本篇主要介绍一下项目中如何使用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
- T 代表已实现状态机的类型 ,, 也就是状态机本体。
- S 代表实现状态的类型 。
- E 代表已实现事件的类型。
- C 代表已实现的外部上下文的类型。
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 , 后续博主会更新自定义注册工厂 以及自定义可扩展的功能