Подобно белке, маленькому , ловкому , умному , бдительному и милому животному, squirrel-foundation призван предоставить легкую , очень гибкую и расширяемую , диагностируемую , простую в использовании и типобезопасную реализацию конечного автомата Java для корпоративного использования.
Вот диаграмма конечного автомата, описывающая изменение состояния банкомата:
Пример кода можно найти в пакете «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>
Чтобы быстро опробовать функции конечного автомата белки, создайте проект 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
После создания конструктора конечных автоматов мы можем использовать свободный API для определения состояния/перехода/действия конечного автомата.
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . GoToB );
Внешний переход строится между состоянием «А» в состояние «Б» и запускается по полученному событию «GoToB».
builder . internalTransition ( TransitionPriority . HIGH ). within ( MyState . A ). on ( MyEvent . WithinA ). perform ( myAction );
Внутренний переход с высоким приоритетом создается внутри состояния «А» при событии «ВнутриА», выполняющем «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" );
Условный переход создается из состояния «C» в состояние «D» в событии «GoToD», когда внешний контекст удовлетворяет ограничению условия, затем вызывается метод действия «myInternalTransitionCall». Пользователь также может использовать MVEL (мощный язык выражений) для описания состояния следующим образом.
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). whenMvel (
"MyCondition:::(context!=null && context.getValue()>80)" ). callMethod ( "myInternalTransitionCall" );
Примечание. Символы «:::» используются для разделения имени условия и выражения условия. «Контекст» — это предопределенная переменная, указывающая на текущий объект контекста.
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» будет вызван этот метод.
protected void transitFromAnyToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
transitFromAnyTo[TargetStateName]On[EventName] Метод будет вызываться при переходе из любого состояния в состояние «B» по событию «GoToB».
protected void exitA ( MyState from , MyState to , MyEvent event , MyContext context )
exit[StateName] Метод будет вызываться при выходе из состояния «A». Итак, запись[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]
Перечисленные выше соглашения о методах также предоставляют АОП-подобные функциональные возможности, которые обеспечивают встроенную возможность гибкого расширения для конечного автомата белки с любой степенью детализации. Для получения дополнительной информации обратитесь к тестовому примеру « org.squirrelframework.foundation.fsm.ExtensionMethodCallTest ». Начиная с версии 0.3.1, существует другой способ определения этих АОП-подобных методов расширения — через свободный API (спасибо за предложение от Виттали), например
// 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 ());
Примечание. Если для определения конечного автомата вы используете только свободный API, нет необходимости реализовывать соответствующие преобразователи. А также, если класс Event или State имеет тип String или Enumeration, в большинстве случаев вам не нужно явно реализовывать или регистрировать преобразователь.
Новый экземпляр конечного автомата
После того, как поведение конечного автомата определено пользователем, пользователь может создать новый экземпляр конечного автомата с помощью компоновщика. Обратите внимание: как только экземпляр конечного автомата создан из построителя, построитель больше не может использоваться для определения какого-либо нового элемента конечного автомата.
T newStateMachine ( S initialStateId , Object ... extraParams );
Чтобы создать новый экземпляр конечного автомата из конструктора конечных автоматов, вам необходимо передать следующие параметры.
initialStateId
: начальное состояние конечного автомата при запуске.
extraParams
: дополнительные параметры, необходимые для создания нового экземпляра конечного автомата. Установите значение «new Object[0]», чтобы дополнительные параметры не требовались.
а. Если пользователь передал дополнительные параметры при создании нового экземпляра конечного автомата, убедитесь, что StateMachineBuilderFactory также определил тип дополнительных параметров при создании построителя конечного автомата. В противном случае дополнительный параметр будет проигнорирован. б. Дополнительные параметры можно передать в экземпляр конечного автомата двумя способами. Один из них — через конструктор конечного автомата, что означает, что пользователю необходимо определить конструктор с тем же типом и порядком параметров для экземпляра конечного автомата. Другой способ — определить метод с именем 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, пользователю необходимо сначала создать UntypedStateMachineBuilder с помощью StateMachineBuilderFactory. StateMachineBuilderFactory принимает только один параметр — тип класса конечного автомата для создания UntypedStateMachineBuilder. @StateMachineParameters используется для объявления типов общих параметров конечного автомата. AbstractUntypedStateMachine — это базовый класс любого нетипизированного конечного автомата.
Контекстно-независимый конечный автомат
Иногда переход состояния не заботится о контексте, что означает, что переход в основном определяется только событием. В этом случае пользователь может использовать контекстно-независимый конечный автомат, чтобы упростить параметры вызова метода. Объявить контекстно-независимый конечный автомат довольно просто. Пользователю нужно только добавить аннотацию @ContextInsensitivity в класс реализации конечного автомата. После этого параметр контекста можно игнорировать в списке параметров метода перехода. например
@ 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) определяет два дочерних состояния «BinA» и «CinA» в родительском состоянии «A», первое определенное дочернее состояние также будет начальным состоянием иерархического состояния «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 определил три типа контекстных событий для разных вариантов использования. Событие запуска/завершения : событие, объявленное как событие запуска/завершения, будет использоваться при запуске/остановке конечного автомата. Таким образом, пользователь может различать триггер вызванного действия, например, когда конечный автомат запускается и входит в исходное состояние, пользователь может различать, что действие входа в состояние было вызвано стартовым событием. Событие завершения : когда все параллельные состояния достигнут конечного состояния, событие завершения будет автоматически запущено. Пользователь может определить следующий переход на основе события завершения. Чтобы определить контекстное событие, у пользователя есть два способа: 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 за решение проблемы 33). Теперь пользователь определяет «HistoryType.DEEP» только на верхнем уровне исторического состояния, и историческая информация всех его дочерних состояний будет запомнена.
Типы переходов
Согласно спецификации UML, переход может быть одним из этих трех видов:
- Внутренний переход Подразумевает, что переход, если он инициирован, происходит без выхода или входа в исходное состояние (т. е. он не вызывает изменения состояния). Это означает, что условие входа или выхода исходного состояния не будет задействовано. Внутренний переход может быть выполнен, даже если StateMachine находится в одном или нескольких регионах, вложенных в связанное состояние.
- Локальный переход подразумевает, что переход, если он запущен, не выйдет из составного (исходного) состояния, но выйдет и повторно войдет в любое состояние внутри составного состояния, которое находится в текущей конфигурации состояния.
- Внешний переход. Подразумевается, что переход, если он сработает, выйдет из составного (исходного) состояния.
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 ) {
// ...
}
});
Пользователь также может добавить прослушиватель для прослушивания TransitionEvent через StateMachine.addTransitionListener, что означает, что все события, возникающие во время каждого перехода состояния, включая 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 ) {
}
Как правило, пользователь может подключить логику бизнес-обработки к этим методам расширения во время каждого перехода между состояниями, в то время как прослушиватель различных событий служит границей системы управления на основе конечного автомата, которая может взаимодействовать с внешними модулями (например, пользовательским интерфейсом, аудитом, 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, например
Action <...> newAction = new Action <...>() {
...
@ Override
public int weight () {
return 100 ;
}
}
squirrel-foundation также поддерживает традиционный способ объявления веса действия. Вес действия вызова метода, имя которого начинается с « до », будет установлен равным 100, поэтому имя, начинающееся с « после », будет установлено в -100. Обычно это означает, что имя метода действия, начинающееся с «до», будет вызываться первым, а имя метода действия, начинающееся с «после», будет вызываться последним. «Метод1:игнорировать» означает, что метод1 не будет вызываться.
Для получения дополнительной информации обратитесь к тестовому примеру « 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 . Пользователь может зарегистрировать экземпляр реализации ExecutorService через SquirrelSingletonProvider , например
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 можно использовать для импорта определения конечного автомата, аналогичного SCXML, которое было экспортировано с помощью SCXMLVisitor, или определения рукописного ввода. 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 );
ПРИМЕЧАНИЕ . Данные конечного автомата можно сериализовать или десериализовать из строки, закодированной Base64, с помощью класса ObjectSerializableSupport .
Конфигурация конечного автомата
При создании нового экземпляра конечного автомата пользователь может настроить его поведение через 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 .
...
Поскольку регистратор конечных автоматов версии 0.3.0 можно использовать более простым способом, просто установив StateMachineConfiguration, включите режим отладки, например
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 в класс конечного автомата, который будет регистрировать все время выполнения метода действия. Например, время выполнения метода транзитаFromAToBOnGoToB будет зарегистрировано.
@ LogExecTime
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
Временное состояние
Временное состояние — это состояние, которое может задерживать или периодически запускать определенное событие после входа в состояние. Временная задача будет отправлена в ScheduledExecutorService . Пользователь может зарегистрировать экземпляр реализации ScheduledExecutorService через SquirrelSingletonProvider , например
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 ));
ПРИМЕЧАНИЕ . Поддержка функции JMX устарела с версии 0.3.9-SNAPSHOT.
См. файл ПРИМЕРЫ.
См. файл ПРИМЕЧАНИЯ К ВЫПУСКУ.
Чтобы быть в курсе последних обновлений, подписывайтесь на мой твиттер @hhe11 или +HeHenry.
Для обсуждений или вопросов присоединяйтесь к группе государственных машин белки.
По любым вопросам или требованиям, пожалуйста, отправьте вопрос
Если вы используете код Squirrel State Machine в своем приложении, я буду признателен, если вы сообщите об этом автору (электронная почта: [email protected]) следующим образом :
Тема: Текст уведомления об использовании конечного автомата Squirrel: Я использую конечный автомат Squirrel <lib_version> в <project_name> — http://link_to_project. Я [разрешаю | не разрешать] упоминать мой проект в разделе «Кто использует Squirrel State Machine» на GitHub.