小さく、機敏で、賢く、機敏でかわいい動物であるリスのように、squirrel-foundation は、エンタープライズ用途向けに、軽量で、柔軟性と拡張性が高く、診断可能で、使いやすく、型安全な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インターフェイスは 4 つのジェネリック型パラメータを取ります。
ステートマシンビルダー
ステート マシンを作成するには、まずステート マシン ビルダーを作成する必要があります。例えば:
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' で状態 'A' 内に構築され、'myAction' を実行します。内部遷移とは、遷移完了後に状態が終了または開始されないことを意味します。遷移の優先順位は、ステート マシンが拡張されたときに元の遷移をオーバーライドするために使用されます。
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 で定義されたステート マシンもこれらのアノテーションによって拡張できます。 (注意が必要な点が 1 つあります。インターフェイス内で定義されたメソッドはパブリックである必要があります。これは、メソッド呼び出しアクションの実装も呼び出し元に対してパブリックになることを意味します。)
コンバーター
@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]」に設定します。
a.新しいステート マシン インスタンスの作成中にユーザーが追加のパラメーターを渡した場合は、ステート マシン ビルダーの作成時に StateMachineBuilderFactory にも定義済みの種類の追加パラメーターがあることを確認してください。それ以外の場合、追加のパラメータは無視されます。 b.追加のパラメータは 2 つの方法でステート マシン インスタンスに渡すことができます。 1 つはステート マシン コンストラクターを使用する方法です。これは、ユーザーがステート マシン インスタンスに対して同じパラメーターの型と順序でコンストラクターを定義する必要があることを意味します。別の方法は、 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 を作成するためのステート マシン クラスのタイプであるパラメーターを 1 つだけ受け取ります。 @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 );
}
}
階層状態の定義
階層状態には、入れ子になった状態が含まれる場合があります。子州自体がネストされた子を持つことができ、ネストは任意の深さまで進めることができます。階層状態がアクティブな場合、その子状態のうち 1 つだけがアクティブになります。階層状態は API またはアノテーションを通じて定義できます。
void defineSequentialStatesOn ( S parentStateId , S ... childStateIds );
builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA) は、親状態「A」の下に 2 つの子状態「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 は、さまざまなユースケースに合わせて 3 種類のコンテキスト イベントを定義しました。開始/終了イベント: 開始/終了イベントとして宣言されたイベントは、ステート マシンの開始/終了時に使用されます。そのため、ユーザーは呼び出されたアクション トリガーを区別できます。たとえば、ステート マシンが起動して初期状態に入るとき、ユーザーはこれらの状態エントリ アクションが開始イベントによって呼び出されたものであることを区別できます。終了イベント: すべての並列状態が最終状態に達すると、終了イベントが自動的に発生します。ユーザーは終了イベントに基づいて次の遷移を定義できます。コンテキスト イベントを定義するには、アノテーションまたはビルダー API の 2 つの方法があります。
@ 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」を定義する必要がありますが、これはあまり便利ではありません。(解決策 Issue33 を提供してくれた Voskuijlen に感謝します)。現在、ユーザーは履歴状態の最上位レベルで「HistoryType.DEEP」を定義するだけで、その子状態の履歴情報はすべて記憶されます。
遷移の種類
UML 仕様によれば、遷移は次の 3 種類のいずれかになります。
- 内部遷移 遷移がトリガーされた場合、ソース状態から出たり入ったりすることなく発生する (つまり、状態変化を引き起こさない) ことを意味します。これは、ソース状態の開始条件または終了条件が呼び出されないことを意味します。内部遷移は、ステートマシンが関連するステート内にネストされた 1 つ以上のリージョンにある場合でも実行できます。
- ローカル遷移 遷移がトリガーされた場合、複合 (ソース) 状態を終了するのではなく、現在の状態構成にある複合状態内の任意の状態を終了して再び入ることを意味します。
- 外部遷移 遷移がトリガーされた場合、複合 (ソース) 状態を終了することを意味します。
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 です。ユーザーはアクションの重みを設定する 2 つの方法があります。
1 つは、メソッド名に重み番号を追加し、「:」で区切る方法です。
// 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 クラスのウェイト メソッドをオーバーライドすることです。
Action <...> newAction = new Action <...>() {
...
@ Override
public int weight () {
return 100 ;
}
}
squirrel-foundation は、アクションの重みを宣言する従来の方法もサポートしています。名前が「 before 」で始まるメソッド呼び出しアクションの重みは 100 に設定されるため、名前が「 after 」で始まる場合は -100 に設定されます。一般に、「before」で始まるアクション メソッド名が最初に呼び出され、「after」で始まるアクション メソッド名が最後に呼び出されることを意味します。 「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 は、ステート マシン ビルダー API や宣言型アノテーションと同様に、ステート マシンを定義するための XML スタイルを提供しました。 SCXML に類似した定義は、標準の SCXML と同等ではありません。
ステート マシン データの保存/ロード
ユーザーはステート マシンがアイドル状態のときにステート マシンのデータを保存できます。
StateMachineData . Reader < MyStateMachine , MyState , MyEvent , MyContext >
savedData = stateMachine . dumpSavedData ();
また、ユーザーは上記のSavedData を、ステータスが終了または初期化されたばかりの別のステート マシンにロードすることもできます。
newStateMachineInstance . loadSavedData ( savedData );
注: ステート マシン データは、 ObjectSerializableSupportクラスを使用して、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 ==========================
アクション メソッドに@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" );
注: 遷移や開始/終了アクションを記述する前に、時間指定された状態を定義する必要があることを確認してください。 0 以下のtimeInterval は、 initialDelayの後に 1 回だけ実行されるとみなされます。
リンク状態 (いわゆるサブマシン状態)
リンクされたステートは、サブマシン ステート マシンの仕様の挿入を指定します。リンクされたステートを含むステート マシンは、包含ステート マシンと呼ばれます。単一のステート マシンを含むコンテキストでは、同じステート マシンが複数回サブマシンになる場合があります。
リンクされた状態は、意味的には複合状態と同等です。サブマシン ステート マシンの領域は、複合状態の領域です。開始、終了、および動作アクションと内部遷移は状態の一部として定義されます。サブマシン状態は、一般的な動作の因数分解とその再利用を可能にする分解メカニズムです。リンク状態は以下のサンプルコードで定義できます。
builderOfTestStateMachine . definedLinkedState ( LState . A , builderOfLinkedStateMachine , LState . A1 );
JMXのサポート
0.3.3 以降、ユーザーはステート マシン インスタンス (例: 現在のステータス、名前) をリモート監視し、実行時に設定を変更 (例: ロギングの切り替え/パフォーマンス モニターの切り替え/リモート起動イベント) できるようになりました。すべてのステート マシン インスタンスの情報は、「org.squirrelframework」ドメインの下にあります。次のサンプル コードは、JMX サポートを有効にする方法を示しています。
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (...);
builder . setStateMachineConfiguration ( StateMachineConfiguration . create (). enableRemoteMonitor ( true ));
注: JMX 機能のサポートは 0.3.9-SNAPSHOT 以降非推奨になりました。
EXAMPLES ファイルを参照してください。
リリースノートファイルを参照してください。
最新情報については、私のツイッター @hhe11 または +HeHenry をフォローしてください。
ディスカッションや質問がある場合は、squirrel ステート マシン グループに参加してください。
問題や要件がある場合は、問題を送信してください
アプリケーションで Squirrel State Machine コードを使用している場合は、次のように作成者 (電子メール: [email protected]) に通知していただければ幸いです。
件名: Squirrel State Machine の使用に関する通知テキスト: <project_name> の Squirrel State Machine <lib_version> を使用しています - http://link_to_project。私は[許可します | GitHub の「Squirrel State Machine を使用している人」セクションで私のプロジェクトに言及することを許可しないでください。