就像松鼠這種小巧、敏捷、聰明、警惕和可愛的動物一樣,松鼠基金會旨在為企業使用提供輕量級、高度靈活和可擴展、可診斷、易於使用和類型安全的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”,這不太方便。現在使用者只需在歷史狀態的頂層定義“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 狀態機”部分提及我的專案。