就像松鼠这种小巧、敏捷、聪明、警惕和可爱的动物一样,松鼠基金会旨在为企业使用提供轻量级、高度灵活和可扩展、可诊断、易于使用和类型安全的Java 状态机实现。
下面是描述 ATM 状态变化的状态机图:
示例代码可以在包“org.squirrelframework.foundation.fsm.atm”中找到。
squirrel-foundation已经部署到maven中央仓库,所以你只需要在pom.xml中添加以下依赖即可。
最新发布版本:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.10</version>
</dependency>
最新快照版本:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.11-SNAPSHOT</version>
</dependency>
为了快速尝试 squirrel 状态机功能,请创建一个 Maven 项目并正确包含 squirrel-foundation 依赖项。然后只需运行以下示例代码即可。
public class QuickStartSample {
// 1. Define State Machine Event
enum FSMEvent {
ToA , ToB , ToC , ToD
}
// 2. Define State Machine Class
@ StateMachineParameters ( stateType = String . class , eventType = FSMEvent . class , contextType = Integer . class )
static class StateMachineSample extends AbstractUntypedStateMachine {
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. Build State Transitions
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create ( StateMachineSample . class );
builder . externalTransition (). from ( "A" ). to ( "B" ). on ( FSMEvent . ToB ). callMethod ( "fromAToB" );
builder . onEntry ( "B" ). callMethod ( "ontoB" );
// 4. Use State Machine
UntypedStateMachine fsm = builder . newStateMachine ( "A" );
fsm . fire ( FSMEvent . ToB , 10 );
System . out . println ( "Current state is " + fsm . getCurrentState ());
}
}
现在您可能对示例代码有很多疑问,请耐心等待。以下用户指南将回答您的大部分问题。但在深入了解细节之前,您需要对状态机概念有基本的了解。这些材料有助于理解状态机概念。 [状态机图] [qt 状态机]
squirrel-foundation支持流畅的 API 和声明式方式来声明状态机,并且还允许用户以简单的方式定义操作方法。
StateMachine接口采用四个泛型类型参数。
状态机生成器
为了创建状态机,用户需要首先创建状态机构建器。例如:
StateMachineBuilder < MyStateMachine , MyState , MyEvent , MyContext > builder =
StateMachineBuilderFactory . create ( MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
状态机构建器采用状态机(T)、状态(S)、事件(E)和上下文(C)类型的参数。
流畅的API
创建状态机构建器后,我们可以使用 Fluent API 来定义状态机的状态/转换/操作。
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . GoToB );
在状态“A”到状态“B”之间构建外部转换,并在接收到事件“GoToB”时触发。
builder . internalTransition ( TransitionPriority . HIGH ). within ( MyState . A ). on ( MyEvent . WithinA ). perform ( myAction );
优先级设置为高的内部转换是在事件“WithinA”执行“myAction”时在状态“A”内构建的。内部转换意味着转换完成后,不退出或进入任何状态。当状态机扩展时,转换优先级用于覆盖原始转换。
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). when (
new Condition < MyContext >() {
@ Override
public boolean isSatisfied ( MyContext context ) {
return context != null && context . getValue ()> 80 ;
}
@ Override
public String name () {
return "MyCondition" ;
}
}). callMethod ( "myInternalTransitionCall" );
当外部上下文满足条件限制时,在事件“GoToD”上构建从状态“C”到状态“D”的条件转换,然后调用操作方法“myInternalTransitionCall”。用户还可以使用MVEL(一种强大的表达语言)按以下方式描述条件。
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). whenMvel (
"MyCondition:::(context!=null && context.getValue()>80)" ). callMethod ( "myInternalTransitionCall" );
注意:字符“:::”用于分隔条件名称和条件表达式。 “context”是指向当前 Context 对象的预定义变量。
builder . onEntry ( MyState . A ). perform ( Lists . newArrayList ( action1 , action2 ))
上面的示例代码中定义了状态进入操作的列表。
方法调用动作
用户可以在定义转换或状态进入/退出期间定义匿名操作。然而,操作代码会分散在很多地方,这可能会使代码难以维护。此外,其他用户无法覆盖这些操作。因此 squirrel-foundation 还支持定义状态机方法调用动作,该动作与状态机类本身一起提供。
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (
MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
builder . externalTransition (). from ( A ). to ( B ). on ( toB ). callMethod ( "fromAToB" );
// All transition action method stays with state machine class
public class MyStateMachine <...> extends AbstractStateMachine <...> {
protected void fromAToB ( MyState from , MyState to , MyEvent event , MyContext context ) {
// this method will be called during transition from "A" to "B" on event "toB"
// the action method parameters types and order should match
...
}
}
此外,squirrel-foundation还支持以约定优于配置的方式定义方法调用动作。基本上,这意味着如果状态机中声明的方法满足命名和参数约定,它将被添加到转换操作列表中,并在某个阶段被调用。例如
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
名为TransitFrom[SourceStateName]To[TargetStateName]On[EventName]且参数化为 [MyState, MyState, MyEvent, MyContext] 的方法将被添加到转换“A-(GoToB)->B”操作列表中。当事件“GoToB”从状态“A”转换到状态“B”时,将调用此方法。
protected void transitFromAnyToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
TransitFromAnyTo[TargetStateName]On[EventName]当事件“GoToB”上从任何状态转移到状态“B”时,将调用该方法。
protected void exitA ( MyState from , MyState to , MyEvent event , MyContext context )
exit[StateName]当退出状态“A”时将调用该方法。因此,如Entry[StateName] 、 beforeExitAny / afterExitAny和beforeEntryAny / afterEntryAny 。
其他支持的命名模式:
transitFrom[fromStateName]To[toStateName]On[eventName]When[conditionName]
transitFrom[fromStateName]To[toStateName]On[eventName]
transitFromAnyTo[toStateName]On[eventName]
transitFrom[fromStateName]ToAnyOn[eventName]
transitFrom[fromStateName]To[toStateName]
on[eventName]
上面列出的方法约定还提供了类似 AOP 的功能,为松鼠状态机提供了任何粒度的内置灵活扩展能力。更多信息,请参考测试用例“ org.squirrelframework.foundation.fsm.ExtensionMethodCallTest ”。从0.3.1开始,还有另一种方法来定义这些类似AOP的扩展方法,即通过Fluent API(感谢vittali的建议),例如
// since 0.3.1
// the same effect as add method transitFromAnyToCOnToC in your state machine
builder . transit (). fromAny (). to ( "C" ). on ( "ToC" ). callMethod ( "fromAnyToC" );
// the same effect as add method transitFromBToAnyOnToC in your state machine
builder . transit (). from ( "B" ). toAny (). on ( "ToC" ). callMethod ( "fromBToAny" );
// the same effect as add method transitFromBToAny in your state machine
builder . transit (). from ( "B" ). toAny (). onAny (). callMethod ( "fromBToAny" );
或者通过声明性注释,例如
// since 0.3.1
@ Transitions ({
@ Transit ( from = "B" , to = "E" , on = "*" , callMethod = "fromBToEOnAny" ),
@ Transit ( from = "*" , to = "E" , on = "ToE" , callMethod = "fromAnyToEOnToE" )
})
注意:这些操作方法将附加到匹配的和已经存在的转换,但不会创建任何新的转换。从 0.3.4 开始,也可以使用以下 API 一次定义一次多个转换,例如
// transitions(A->B@A2B=>a2b, A->C@A2C=>a2c, A->D@A2D) will be defined at once
builder . transitions (). from ( State . _A ). toAmong ( State . B , State . C , State . D ).
onEach ( Event . A2B , Event . A2C , Event . A2D ). callMethod ( "a2b|a2c|_" );
// transitions(A->_A@A2ANY=>DecisionMaker, _A->A@ANY2A) will be defined at once
builder . localTransitions (). between ( State . A ). and ( State . _A ).
onMutual ( Event . A2ANY , Event . ANY2A ).
perform ( Lists . newArrayList ( new DecisionMaker ( "SomeLocalState" ), null ) );
更多信息可以在org.squirrelframework.foundation.fsm.samples.DecisionStateSampleTest中找到;
声明性注释
还提供了一种声明性方式来定义和扩展状态机。这是一个例子。
@ States ({
@ State ( name = "A" , entryCallMethod = "entryStateA" , exitCallMethod = "exitStateA" ),
@ State ( name = "B" , entryCallMethod = "entryStateB" , exitCallMethod = "exitStateB" )
})
@ Transitions ({
@ Transit ( from = "A" , to = "B" , on = "GoToB" , callMethod = "stateAToStateBOnGotoB" ),
@ Transit ( from = "A" , to = "A" , on = "WithinA" , callMethod = "stateAToStateAOnWithinA" , type = TransitionType . INTERNAL )
})
interface MyStateMachine extends StateMachine < MyStateMachine , MyState , MyEvent , MyContext > {
void entryStateA ( MyState from , MyState to , MyEvent event , MyContext context );
void stateAToStateBOnGotoB ( MyState from , MyState to , MyEvent event , MyContext context )
void stateAToStateAOnWithinA ( MyState from , MyState to , MyEvent event , MyContext context )
void exitStateA ( MyState from , MyState to , MyEvent event , MyContext context );
...
}
该注解既可以定义在状态机的实现类中,也可以定义在状态机将要实现的任何接口中。它还可以与 Fluent API 混合使用,这意味着 Fluent API 中定义的状态机也可以通过这些注释进行扩展。 (您可能需要注意的一件事是,接口中定义的方法必须是公共的,这意味着方法调用操作的实现也将对调用者公开。)
转换器
为了在@State和@Transit中声明状态和事件,用户需要为其状态(S)和事件(E)类型实现相应的转换器。转换必须实现 Converter<T> 接口,该接口将状态/事件与字符串相互转换。
public interface Converter < T > extends SquirrelComponent {
/**
* Convert object to string.
* @param obj converted object
* @return string description of object
*/
String convertToString ( T obj );
/**
* Convert string to object.
* @param name name of the object
* @return converted object
*/
T convertFromString ( String name );
}
然后将这些转换器注册到ConverterProvider 。例如
ConverterProvider . INSTANCE . register ( MyEvent . class , new MyEventConverter ());
ConverterProvider . INSTANCE . register ( MyState . class , new MyStateConverter ());
注意:如果仅使用 Fluent API 来定义状态机,则无需实现相应的转换器。而且,如果 Event 或 State 类是 String 或 Enumeration 类型,则在大多数情况下您不需要显式实现或注册转换器。
新状态机实例
在用户定义状态机行为之后,用户可以通过构建器创建新的状态机实例。请注意,一旦从构建器创建了状态机实例,构建器就不能再用于定义状态机的任何新元素。
T newStateMachine ( S initialStateId , Object ... extraParams );
要从状态机构建器创建新的状态机实例,您需要传递以下参数。
initialStateId
:启动时,状态机的初始状态。
extraParams
:创建新状态机实例所需的额外参数。设置为“new Object[0]” ,无需额外参数。
一个。如果用户在创建新的状态机实例时传递了额外的参数,请确保 StateMachineBuilderFactory 在创建状态机构建器时也定义了额外参数的类型。否则,额外的参数将被忽略。 b.额外的参数可以通过两种方式传递到状态机实例中。一种是通过状态机构造函数,这意味着用户需要为状态机实例定义一个具有相同参数类型和顺序的构造函数。另一种方法是定义一个名为postConstruct
的方法,并具有相同的参数类型和顺序。
如果不需要向状态机传递额外的参数,用户可以简单地调用T newStateMachine(S initialStateId)
来创建一个新的状态机实例。
来自状态机制造商的新状态机。 (在这种情况下,不需要传递额外的参数。)
MyStateMachine stateMachine = builder . newStateMachine ( MyState . Initial );
触发转换
创建状态机后,用户可以触发事件以及上下文来触发状态机内的转换。例如
stateMachine . fire ( MyEvent . Prepare , new MyContext ( "Testing" ));
无类型状态机
为了简化状态机的使用,并避免过多的泛型类型(例如 StateMachine<T, S, E, C>),这可能会使代码在某些情况下难以阅读,但仍然保留转换操作上类型安全功能的重要部分执行时,UntypedStateMachine就是为了这个目的而实现的。
enum TestEvent {
toA , toB , toC , toD
}
@ Transitions ({
@ Transit ( from = "A" , to = "B" , on = "toB" , callMethod = "fromAToB" ),
@ Transit ( from = "B" , to = "C" , on = "toC" ),
@ Transit ( from = "C" , to = "D" , on = "toD" )
})
@ StateMachineParameters ( stateType = String . class , eventType = TestEvent . class , contextType = Integer . class )
class UntypedStateMachineSample extends AbstractUntypedStateMachine {
// No need to specify constructor anymore since 0.2.9
// protected UntypedStateMachineSample(ImmutableUntypedState initialState,
// Map<Object, ImmutableUntypedState> states) {
// super(initialState, states);
// }
protected void fromAToB ( String from , String to , TestEvent event , Integer context ) {
// transition action still type safe ...
}
protected void transitFromDToAOntoA ( String from , String to , TestEvent event , Integer context ) {
// transition action still type safe ...
}
}
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (
UntypedStateMachineSample . class );
// state machine builder not type safe anymore
builder . externalTransition (). from ( "D" ). to ( "A" ). on ( TestEvent . toA );
UntypedStateMachine fsm = builder . newStateMachine ( "A" );
要构建一个UntypedStateMachine,用户需要首先通过StateMachineBuilderFactory创建一个UntypedStateMachineBuilder。 StateMachineBuilderFactory只需要一个参数,即状态机类的类型来创建UntypedStateMachineBuilder。 @StateMachineParameters用于声明状态机通用参数类型。 AbstractUntypedStateMachine是任何无类型状态机的基类。
上下文不敏感状态机
有时状态转换并不关心上下文,这意味着状态转换大多仅由事件决定。对于这种情况,用户可以使用上下文不敏感的状态机来简化方法调用参数。声明上下文不敏感的状态机非常简单。用户只需在状态机实现类上添加注解@ContextInsensitive即可。之后,转换方法参数列表中的上下文参数可以被忽略。例如
@ ContextInsensitive
public class ATMStateMachine extends AbstractStateMachine < ATMStateMachine , ATMState , String , Void > {
// no need to add context parameter here anymore
public void transitFromIdleToLoadingOnConnected ( ATMState from , ATMState to , String event ) {
...
}
public void entryLoading ( ATMState from , ATMState to , String event ) {
...
}
}
转换异常处理
当状态转换过程中发生异常时,已执行的操作列表将被中止,状态机将进入错误状态,这意味着状态机实例无法再处理事件。如果用户继续向状态机实例触发事件,则会抛出 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 );
}
}
定义层次状态
分层状态可以包含嵌套状态。子状态本身可能有嵌套的子状态,并且嵌套可以进行到任何深度。当分层状态处于活动状态时,其一个且只有一个子状态处于活动状态。分层状态可以通过API或注释来定义。
void defineSequentialStatesOn ( S parentStateId , S ... childStateIds );
builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA)在父状态“A”下定义两个子状态“BinA”和“CinA”,第一个定义的子状态也将是层次状态“A”的初始状态。相同的层次状态也可以通过注释来定义,例如
@ States ({
@ State ( name = "A" , entryMethodCall = "entryA" , exitMethodCall = "exitA" ),
@ State ( parent = "A" , name = "BinA" , entryMethodCall = "entryBinA" , exitMethodCall = "exitBinA" , initialState = true ),
@ State ( parent = "A" , name = "CinA" , entryMethodCall = "entryCinA" , exitMethodCall = "exitCinA" )
})
定义并行状态
并行状态封装了一组子状态,当父元素处于活动状态时,这些子状态同时处于活动状态。并行状态可以通过 API 或注释来定义。例如
// defines two region states "RegionState1" and "RegionState2" under parent parallel state "Root"
builder . defineParallelStatesOn ( MyState . Root , MyState . RegionState1 , MyState . RegionState2 );
builder . defineSequentialStatesOn ( MyState . RegionState1 , MyState . State11 , MyState . State12 );
builder . externalTransition (). from ( MyState . State11 ). to ( MyState . State12 ). on ( MyEvent . Event1 );
builder . defineSequentialStatesOn ( MyState . RegionState2 , MyState . State21 , MyState . State22 );
builder . externalTransition (). from ( MyState . State21 ). to ( MyState . State22 ). on ( MyEvent . Event2 );
或者
@ States ({
@ State ( name = "Root" , entryCallMethod = "enterRoot" , exitCallMethod = "exitRoot" , compositeType = StateCompositeType . PARALLEL ),
@ State ( parent = "Root" , name = "RegionState1" , entryCallMethod = "enterRegionState1" , exitCallMethod = "exitRegionState1" ),
@ State ( parent = "Root" , name = "RegionState2" , entryCallMethod = "enterRegionState2" , exitCallMethod = "exitRegionState2" )
})
获取并行状态的当前子状态
stateMachine . getSubStatesOn ( MyState . Root ); // return list of current sub states of parallel state
当所有并行状态达到最终状态时,将触发Finish上下文事件。
定义上下文事件
上下文事件意味着用户定义的事件在状态机中具有预定义的上下文。 squirrel-foundation 为不同的用例定义了三种类型的上下文事件。启动/终止事件:声明为启动/终止事件的事件将在状态机启动/终止时使用。因此,用户可以区分调用的动作触发器,例如,当状态机启动并进入其初始状态时,用户可以区分这些状态进入动作是由启动事件调用的。 Finish 事件:当所有并行状态达到最终状态时,finish 事件将自动触发。用户可以根据完成事件定义以下转换。要定义上下文事件,用户有两种方式:注释或构建器 API。
@ ContextEvent ( finishEvent = "Finish" )
static class ParallelStateMachine extends AbstractStateMachine <...> {
}
或者
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (...);
...
builder . defineFinishEvent ( HEvent . Start );
builder . defineTerminateEvent ( HEvent . Terminate );
builder . defineStartEvent ( HEvent . Finish );
使用历史状态保存和恢复当前状态
历史伪状态允许状态机记住其状态配置。以历史状态作为目标的转换将使状态机返回到此记录的配置。如果历史的“类型”是“浅”,则状态机处理器必须在进行退出父级的任何转换之前记录其父级的直接活动子级。如果历史记录的“类型”是“深”,则状态机处理器必须在进行退出父级的任何转换之前记录父级的所有活动后代。支持 API 和注释来定义状态的历史类型。例如
// defined history type of state "A" as "deep"
builder . defineSequentialStatesOn ( MyState . A , HistoryType . DEEP , MyState . A1 , MyState . A2 )
或者
@ State ( parent = "A" , name = "A1" , entryCallMethod = "enterA1" , exitCallMethod = "exitA1" , historyType = HistoryType . DEEP )
注意: 0.3.7之前,用户需要为每一级历史状态定义“HistoryType.DEEP”,这不太方便。(感谢Voskuijlen提供解决方案Issue33)。现在用户只需在历史状态的顶层定义“HistoryType.DEEP”,它的所有子状态历史信息都会被记住。
过渡类型
根据 UML 规范,转换可能是以下三种类型之一:
- 内部转换意味着转换如果被触发,则在不退出或进入源状态的情况下发生(即,它不会导致状态更改)。这意味着源状态的进入或退出条件不会被调用。即使 StateMachine 位于关联 State 内嵌套的一个或多个 Region 中,也可以进行内部 Transition。
- 本地转换意味着转换如果被触发,将不会退出复合(源)状态,但它将退出并重新进入当前状态配置中的复合状态中的任何状态。
- 外部转换意味着转换如果被触发,将退出复合(源)状态
squirrel-foundation 支持 API 和注释来声明各种转换,例如
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . A2B );
builder . internalTransition (). within ( MyState . A ). on ( MyEvent . innerA );
builder . localTransition (). from ( MyState . A ). to ( MyState . CinA ). on ( MyEvent . intoC )
或者
@ Transitions ({
@ Transition ( from = "A" , to = "B" , on = "A2B" ), //default value of transition type is EXTERNAL
@ Transition ( from = "A" , on = "innerA" , type = TransitionType . INTERNAL ),
@ Transition ( from = "A" , to = "CinA" , on = "intoC" , type = TransitionType . LOCAL ),
})
多态事件调度
在状态机的生命周期中,会触发各种事件,例如
State Machine Lifecycle Events
|--StateMachineEvent /* Base event of all state machine event */
|--StartEvent /* Fired when state machine started */
|--TerminateEvent /* Fired when state machine terminated */
|--TransitionEvent /* Base event of all transition event */
|--TransitionBeginEvent /* Fired when transition began */
|--TransitionCompleteEvent /* Fired when transition completed */
|--TransitionExceptionEvent /* Fired when transition threw exception */
|--TransitionDeclinedEvent /* Fired when transition declined */
|--TransitionEndEvent /* Fired when transition end no matter declined or complete */
用户可以添加一个侦听器来侦听 StateMachineEvent,这意味着状态机生命周期期间触发的所有事件都将被该侦听器捕获,例如:
stateMachine . addStateMachineListener ( new StateMachineListener <...>() {
@ Override
public void stateMachineEvent ( StateMachineEvent <...> event ) {
// ...
}
});
用户还可以通过 StateMachine.addTransitionListener 添加监听器来监听 TransitionEvent,这意味着每次状态转换期间触发的所有事件(包括 TransitionBeginEvent、TransitionCompleteEvent 和 TransitionEndEvent)都将被该监听器捕获。或者用户可以添加特定的侦听器(例如 TransitionDeclinedListener)来在转换请求被拒绝时侦听 TransitionDeclinedEvent。
声明式事件监听器
将上述事件侦听器添加到状态机有时会让用户感到烦恼,并且太多的泛型类型也会使代码难以阅读。为了简化状态机的使用,更重要的是提供非侵入式集成,squirrel-foundation 提供了一种声明式方式通过以下注释添加事件监听器,例如
static class ExternalModule {
@ OnTransitionEnd
@ ListenerOrder ( 10 ) // Since 0.3.1 ListenerOrder can be used to insure listener invoked orderly
public void transitionEnd () {
// method annotated with TransitionEnd will be invoked when transition end...
// the method must be public and return nothing
}
@ OnTransitionBegin
public void transitionBegin ( TestEvent event ) {
// method annotated with TransitionBegin will be invoked when transition begin...
}
// 'event'(E), 'from'(S), 'to'(S), 'context'(C) and 'stateMachine'(T) can be used in MVEL scripts
@ OnTransitionBegin ( when = "event.name().equals( " toB " )" )
public void transitionBeginConditional () {
// method will be invoked when transition begin while transition caused by event "toB"
}
@ OnTransitionComplete
public void transitionComplete ( String from , String to , TestEvent event , Integer context ) {
// method annotated with TransitionComplete will be invoked when transition complete...
}
@ OnTransitionDecline
public void transitionDeclined ( String from , TestEvent event , Integer context ) {
// method annotated with TransitionDecline will be invoked when transition declined...
}
@ OnBeforeActionExecuted
public void onBeforeActionExecuted ( Object sourceState , Object targetState ,
Object event , Object context , int [] mOfN , Action <?, ?, ?,?> action ) {
// method annotated with OnAfterActionExecuted will be invoked before action invoked
}
@ OnAfterActionExecuted
public void onAfterActionExecuted ( Object sourceState , Object targetState ,
Object event , Object context , int [] mOfN , Action <?, ?, ?,?> action ) {
// method annotated with OnAfterActionExecuted will be invoked after action invoked
}
@ OnActionExecException
public void onActionExecException ( Action <?, ?, ?,?> action , TransitionException e ) {
// method annotated with OnActionExecException will be invoked when action thrown exception
}
}
ExternalModule externalModule = new ExternalModule ();
fsm . addDeclarativeListener ( externalModule );
...
fsm . removeDeclarativeListener ( externalModule );
通过执行此外部模块代码,不需要实现任何状态机侦听器接口。仅在过渡阶段挂钩的方法上添加少量注释。方法的参数也是类型安全的,并且会自动推断以匹配相应的事件。这是关注点分离的好方法。用户可以在org.squirrelframework.foundation.fsm.StateMachineLogger中找到示例用法。
过渡扩展方法
每个转换事件在 AbstractStateMachine 类上也有相应的扩展方法,允许在客户状态机实现类中进行扩展。
protected void afterTransitionCausedException ( Exception e , S fromState , S toState , E event , C context ) {
}
protected void beforeTransitionBegin ( S fromState , E event , C context ) {
}
protected void afterTransitionCompleted ( S fromState , S toState , E event , C context ) {
}
protected void afterTransitionEnd ( S fromState , S toState , E event , C context ) {
}
protected void afterTransitionDeclined ( S fromState , E event , C context ) {
}
protected void beforeActionInvoked ( S fromState , S toState , E event , C context ) {
}
通常,用户可以在每次状态转换期间将业务处理逻辑挂接到这些扩展方法中,而各种事件侦听器作为基于状态机的控制系统的边界,可以与外部模块(例如 UI、审计、ESB 等)进行交互)。例如,用户可以扩展afterTransitionCausedException方法,用于在转换过程中发生异常时进行环境清理,也可以通过TransitionExceptionEvent通知用户界面模块显示错误消息。
加权行动
用户可以定义动作权重来调整动作执行顺序。状态进入/退出和状态转换期间的动作按照其权重值升序排列。动作权重默认为 0。用户有两种方式设置动作重量。
一种是将权重数字附加到方法名称并用“:”分隔。
// define state entry action 'goEntryD' weight -150
@ State ( name = "D" , entryCallMethod = "goEntryD:-150" )
// define transition action 'goAToC1' weight +150
@ Transit ( from = "A" , to = "C" , on = "ToC" , callMethod = "goAToC1:+150" )
另一种方法是重写Action类的weight方法,例如
Action <...> newAction = new Action <...>() {
...
@ Override
public int weight () {
return 100 ;
}
}
squirrel-foundation也支持传统的方式来声明action的权重。名称以“ before ”开头的方法调用操作的权重将设置为100,因此名称以“ after ”开头的方法调用操作的权重将设置为-100。一般表示以before开头的action方法会先被调用,以after开头的action方法最后会被调用。 “method1:ignore”表示不会调用method1。
更多信息请参考测试用例 ' org.squirrelframework.foundation.fsm.WeightedActionTest ';
异步执行
@AsyncExecute注解可以用在方法调用操作和声明性事件监听器上,以指示该操作或事件监听器将异步执行,例如定义异步调用的操作方法:
@ ContextInsensitive
@ StateMachineParameters ( stateType = String . class , eventType = String . class , contextType = Void . class )
public class ConcurrentSimpleStateMachine extends AbstractUntypedStateMachine {
// No need to specify constructor anymore since 0.2.9
// protected ConcurrentSimpleStateMachine(ImmutableUntypedState initialState,
// Map<Object, ImmutableUntypedState> states) {
// super(initialState, states);
// }
@ AsyncExecute
protected void fromAToB ( String from , String to , String event ) {
// this action method will be invoked asynchronously
}
}
定义异步调度事件:
public class DeclarativeListener {
@ OnTransitionBegin
@ AsyncExecute
public void onTransitionBegin (...) {
// transition begin event will be dispatched asynchronously to this listener method
}
}
异步执行任务将提交给ExecutorService 。用户可以通过SquirrelSingletonProvider注册您的 ExecutorService 实现实例,例如
ExecutorService executorService = Executors . newFixedThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ExecutorService . class , executorService );
如果没有注册 ExecutorService 实例, SquirrelConfiguration将提供一个默认实例。
状态机后处理器
用户可以为特定类型的状态机注册后处理器,以便在状态机实例化后添加后处理逻辑,例如
// 1 User defined a state machine interface
interface MyStateMachine extends StateMachine < MyStateMachine , MyState , MyEvent , MyContext > {
. . .
}
// 2 Both MyStateMachineImpl and MyStateMachineImplEx are implemented MyStateMachine
class MyStateMachineImpl implements MyStateMachine {
. . .
}
class MyStateMachineImplEx implements MyStateMachine {
. . .
}
// 3 User define a state machine post processor
MyStateMachinePostProcessor implements SquirrelPostProcessor < MyStateMachine > {
void postProcess ( MyStateMachine component ) {
. . .
}
}
// 4 User register state machine post process
SquirrelPostProcessorProvider . getInstance (). register ( MyStateMachine . class , MyStateMachinePostProcessor . class );
对于这种情况,当用户创建 MyStateMachineImpl 和 MyStateMachineImplEx 实例时,将调用注册的后处理器 MyStateMachinePostProcessor 来执行一些工作。
状态机导出
SCXMLVisitor可用于导出 [SCXML] 2 文档中的状态机定义。
SCXMLVisitor visitor = SquirrelProvider . getInstance (). newInstance ( SCXMLVisitor . class );
stateMachine . accept ( visitor );
visitor . convertSCXMLFile ( "MyStateMachine" , true );
顺便说一句,用户还可以调用StateMachine.exportXMLDefinition(true)导出美化的 XML 定义。 DotVisitor可以用来生成状态图,可以通过[GraphViz]3查看。
DotVisitor visitor = SquirrelProvider . getInstance (). newInstance ( DotVisitor . class );
stateMachine . accept ( visitor );
visitor . convertDotFile ( "SnakeStateMachine" );
状态机导入
UntypedStateMachineImporter可用于导入由 SCXMLVisitor 或手写定义导出的状态机 SCXML 类似定义。 UntypedStateMachineImporter 将根据定义构建一个 UntypedStateMachineBuilder,稍后可用于创建状态机实例。
UntypedStateMachineBuilder builder = new UntypedStateMachineImporter (). importDefinition ( scxmlDef );
ATMStateMachine stateMachine = builder . newAnyStateMachine ( ATMState . Idle );
注意:UntypedStateMachineImporter 提供了 XML 样式来定义状态机,就像状态机构建器 API 或声明性注释一样。与 SCXML 类似的定义不等于标准 SCXML。
保存/加载状态机数据
当状态机处于空闲状态时,用户可以保存状态机的数据。
StateMachineData . Reader < MyStateMachine , MyState , MyEvent , MyContext >
savedData = stateMachine . dumpSavedData ();
用户还可以将上述保存的数据加载到另一个状态机,其状态已终止或刚刚初始化。
newStateMachineInstance . loadSavedData ( savedData );
注意:在ObjectSerializedSupport类的帮助下,状态机数据可以序列化为 Base64 编码字符串或从 Base64 编码字符串反序列化。
状态机配置
当创建新的状态机实例时,用户可以通过StateMachineConfiguration配置其行为,例如
UntypedStateMachine fsm = builder . newUntypedStateMachine ( "a" ,
StateMachineConfiguration . create (). enableAutoStart ( false )
. setIdProvider ( IdProvider . UUIDProvider . getInstance ()),
new Object [ 0 ]); // since 0.3.0
fsm . fire ( TestEvent . toA );
上面的示例代码用于创建一个以UUID为标识符的状态机实例并禁用自动启动功能。 StateMachineConfigure 也可以在状态机构建器上设置,这意味着由builder.newStateMachine(S initialStateId)
或builder.newStateMachine(S initialStateId, Object... extraParams)
创建的所有状态机实例都将使用此配置。
状态机诊断
StateMachineLogger用于观察状态机的内部状态,如执行性能、动作调用顺序、转换进度等,例如
StateMachine <?,?,?,?> stateMachine = builder . newStateMachine ( HState . A );
StateMachineLogger fsmLogger = new StateMachineLogger ( stateMachine );
fsmLogger . startLogging ();
...
stateMachine . fire ( HEvent . B2A , 1 );
...
fsmLogger . terminateLogging ();
-------------------------------------------------------------------------------------------
Console Log :
HierachicalStateMachine : Transition from "B2a" on "B2A" with context "1" begin .
Before execute method call action "leftB2a" ( 1 of 6 ).
Before execute method call action "exitB2" ( 2 of 6 ).
...
Before execute method call action "entryA1" ( 6 of 6 ).
HierachicalStateMachine : Transition from "B2a" to "A1" on "B2A" complete which took 2 ms .
...
由于 v0.3.0 状态机记录器可以通过更简单的方式使用,只需将 StateMachineConfiguration 启用调试模式设置为 ture,例如
StateMachine<?,?,?,?> stateMachine = builder.newStateMachine(HState.A,
StateMachineConfiguration.create().enableDebugMode(true),
new Object[0]);
StateMachinePerformanceMonitor可用于监控状态机执行性能信息,包括总转换次数、平均转换消耗时间等,例如
final UntypedStateMachine fsm = builder . newStateMachine ( "D" );
final StateMachinePerformanceMonitor performanceMonitor =
new StateMachinePerformanceMonitor ( "Sample State Machine Performance Info" );
fsm . addDeclarativeListener ( performanceMonitor );
for ( int i = 0 ; i < 10000 ; i ++) {
fsm . fire ( FSMEvent . ToA , 10 );
fsm . fire ( FSMEvent . ToB , 10 );
fsm . fire ( FSMEvent . ToC , 10 );
fsm . fire ( FSMEvent . ToD , 10 );
}
fsm . removeDeclarativeListener ( performanceMonitor );
System . out . println ( performanceMonitor . getPerfModel ());
-------------------------------------------------------------------------------------------
Console Log :
========================== Sample State Machine Performance Info ==========================
Total Transition Invoked : 40000
Total Transition Failed : 0
Total Transition Declained : 0
Average Transition Comsumed : 0.0004 ms
Transition Key Invoked Times Average Time Max Time Min Time
C --{ ToD , 10 }-> D 10000 0.0007 ms 5 ms 0 ms
B --{ ToC , 10 }-> C 10000 0.0001 ms 1 ms 0 ms
D --{ ToA , 10 }-> A 10000 0.0009 ms 7 ms 0 ms
A --{ ToB , 10 }-> B 10000 0.0000 ms 1 ms 0 ms
Total Action Invoked : 40000
Total Action Failed : 0
Average Action Execution Comsumed : 0.0000 ms
Action Key Invoked Times Average Time Max Time Min Time
instan ... Test$1 40000 0.0000 ms 1 ms 0 ms
========================== Sample State Machine Performance Info ==========================
在action方法上添加@LogExecTime将记录该方法的执行时间。并且还在状态机类上添加@LogExecTime将注销所有操作方法的执行时间。例如, transitFromAToBOnGoToB方法的执行时间将被注销。
@ LogExecTime
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
定时状态
定时状态是进入状态后可以延迟或周期性触发指定事件的状态。定时任务将提交给ScheduledExecutorService 。用户可以通过SquirrelSingletonProvider注册您的 ScheduledExecutorService 实现实例,例如
ScheduledExecutorService scheduler = Executors . newScheduledThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ScheduledExecutorService . class , scheduler );
如果没有注册 ScheduledExecutorService 实例, SquirrelConfiguration将提供一个默认实例。之后,可以由状态机构建器定义定时状态,例如
// after 50ms delay fire event "FIRST" every 100ms with null context
builder . defineTimedState ( "A" , 50 , 100 , "FIRST" , null );
builder . internalTransition (). within ( "A" ). on ( "FIRST" );
注意:确保在描述其转换或进入/退出操作之前必须定义定时状态。 timeInterval小于或等于 0 将被视为仅在initialDelay之后执行一次。
链接状态(所谓的子机器状态)
链接状态指定子机状态机规范的插入。包含链接状态的状态机称为包含状态机。在单个包含状态机的上下文中,同一状态机可以多次作为子机。
链接状态在语义上等同于复合状态。子机状态机的区域是复合状态的区域。进入、退出、行为动作和内部转换被定义为状态的一部分。子机状态是一种分解机制,允许分解常见行为及其重用。链接状态可以通过以下示例代码定义。
builderOfTestStateMachine . definedLinkedState ( LState . A , builderOfLinkedStateMachine , LState . A1 );
JMX 支持
从0.3.3开始,用户可以在运行时远程监视状态机实例(例如当前状态、名称)并修改配置(例如切换日志记录/切换性能监视器/远程触发事件)。所有状态机实例信息都将位于“org.squirrelframework”域下。以下示例代码展示了如何启用 JMX 支持。
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (...);
builder . setStateMachineConfiguration ( StateMachineConfiguration . create (). enableRemoteMonitor ( true ));
注意:自 0.3.9-SNAPSHOT 起,JMX 功能支持已弃用。
请参阅示例文件。
请参阅发行说明文件。
如需了解最新更新,请关注我的推特 @hhe11 或 +HeHenry
讨论或提问请加入松鼠状态机群
对于任何问题或要求,请提交问题
如果您在应用程序中使用 Squirrel 状态机代码,请像这样告知作者(电子邮件:[email protected]),我将不胜感激:
主题: Squirrel 状态机使用通知文本:我在 <project_name> - http://link_to_project 中使用 Squirrel 状态机 <lib_version>。我[允许|不允许]在 GitHub 上的“谁使用 Squirrel 状态机”部分提及我的项目。