Assim como o esquilo, um animal pequeno , ágil , inteligente , alerta e fofo , a squirrel-foundation tem como objetivo fornecer uma implementação de máquina de estado Java leve , altamente flexível e extensível , diagnosticável , fácil de usar e de tipo seguro para uso corporativo.
Aqui está o diagrama da máquina de estado que descreve a mudança de estado de um ATM:
O código de amostra pode ser encontrado no pacote "org.squirrelframework.foundation.fsm.atm" .
squirrel-foundation foi implantado no repositório central maven, então você só precisa adicionar a seguinte dependência ao pom.xml.
Última versão lançada:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.10</version>
</dependency>
Versão mais recente do instantâneo:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.11-SNAPSHOT</version>
</dependency>
Para testar rapidamente as funções da máquina de estado do esquilo, crie um projeto maven e inclua a dependência do squirrel-foundation corretamente. Em seguida, basta executar o seguinte código de exemplo.
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 ());
}
}
No momento você pode ter muitas dúvidas sobre o código de exemplo, por favor, seja paciente. O guia do usuário a seguir responderá à maioria de suas perguntas. Mas antes de entrar em detalhes, é necessário que você tenha um conhecimento básico dos conceitos de máquinas de estado. Esses materiais são bons para compreender os conceitos de máquinas de estado. [diagramas de máquina de estado] [máquina de estado qt]
squirrel-foundation suporta API fluente e maneira declarativa para declarar uma máquina de estado, e também permite que o usuário defina os métodos de ação de maneira direta.
A interface StateMachine usa quatro parâmetros de tipo genérico.
Construtor de máquinas de estado
Para criar uma máquina de estado, o usuário precisa primeiro criar um construtor de máquina de estado. Por exemplo:
StateMachineBuilder < MyStateMachine , MyState , MyEvent , MyContext > builder =
StateMachineBuilderFactory . create ( MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
O construtor de máquina de estado utiliza parâmetros que são tipo de máquina de estado (T), estado (S), evento (E) e contexto (C).
API Fluente
Após a criação do construtor de máquina de estado, podemos usar a API fluente para definir o estado/transição/ação da máquina de estado.
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . GoToB );
Uma transição externa é construída entre o estado 'A' para o estado 'B' e acionada no evento recebido 'GoToB'.
builder . internalTransition ( TransitionPriority . HIGH ). within ( MyState . A ). on ( MyEvent . WithinA ). perform ( myAction );
Uma transição interna com prioridade definida como alta é construída dentro do estado 'A' no evento 'WithinA' e executa 'myAction'. A transição interna significa que após a conclusão da transição, nenhum estado é encerrado ou inserido. A prioridade de transição é usada para substituir a transição original quando a máquina de estado é estendida.
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" );
Uma transição condicional é construída do estado 'C' para o estado 'D' no evento 'GoToD' quando o contexto externo satisfaz a restrição da condição e, em seguida, chama o método de ação "myInternalTransitionCall". O usuário também pode usar MVEL (uma poderosa linguagem de expressão) para descrever condições da seguinte maneira.
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). whenMvel (
"MyCondition:::(context!=null && context.getValue()>80)" ). callMethod ( "myInternalTransitionCall" );
Nota: Os caracteres ':::' são usados para separar o nome da condição e a expressão da condição. O 'contexto' é o ponto variável predefinido para o objeto Context atual.
builder . onEntry ( MyState . A ). perform ( Lists . newArrayList ( action1 , action2 ))
Uma lista de ações de entrada de estado é definida no código de exemplo acima.
Ação de chamada de método
O usuário pode definir ações anônimas durante transições definidas ou entrada/saída de estado. No entanto, o código de ação estará espalhado por muitos lugares, o que pode dificultar a manutenção do código. Além disso, outro usuário não pode substituir as ações. Portanto, o squirrel-foundation também oferece suporte para definir a ação de chamada do método da máquina de estado que vem junto com a própria classe da máquina de estado.
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
...
}
}
Além disso, o squirrel-foundation também suporta a definição de ações de chamada de método de maneira convencional sobre configuração . Basicamente, isso significa que se o método declarado na máquina de estado satisfizer a convenção de nomenclatura e parâmetros, ele será adicionado à lista de ações de transição e também invocado em determinada fase. por exemplo
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
O método denominado transitFrom[SourceStateName]To[TargetStateName]On[EventName] e parametrizado como [MyState, MyState, MyEvent, MyContext] será adicionado à lista de ações de transição "A-(GoToB)->B". Ao transitar do estado 'A' para o estado 'B' no evento 'GoToB', este método será invocado.
protected void transitFromAnyToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
transitFromAnyTo[TargetStateName]On[EventName] O método será invocado quando transitar de qualquer estado para o estado 'B' no evento 'GoToB'.
protected void exitA ( MyState from , MyState to , MyEvent event , MyContext context )
exit[StateName] O método será invocado quando sair do estado 'A'. Assim como a entrada[StateName] , beforeExitAny / afterExitAny e beforeEntryAny / afterEntryAny .
Outros padrões de nomenclatura suportados:
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]
As convenções de método listadas acima também forneceram funcionalidades semelhantes a AOP , que forneceram capacidade de extensão flexível integrada para máquina de estado esquilo em qualquer granularidade. Para obter mais informações, consulte o caso de teste " org.squirrelframework.foundation.fsm.ExtensionMethodCallTest ". Desde 0.3.1, existe outra maneira de definir esses métodos de extensão do tipo AOP que é através de API fluente (graças à sugestão de vittali), por exemplo
// 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" );
Ou através de anotação declarativa, por exemplo
// since 0.3.1
@ Transitions ({
@ Transit ( from = "B" , to = "E" , on = "*" , callMethod = "fromBToEOnAny" ),
@ Transit ( from = "*" , to = "E" , on = "ToE" , callMethod = "fromAnyToEOnToE" )
})
Nota : Esses métodos de ação serão anexados a transições correspondentes e já existentes, mas não para criar novas transições. Desde a versão 0.3.4, múltiplas transições também podem ser definidas uma vez por vez usando a seguinte API, por exemplo
// 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 ) );
Mais informações podem ser encontradas em org.squirrelframework.foundation.fsm.samples.DecisionStateSampleTest ;
Anotação Declarativa
Uma forma declarativa também é fornecida para definir e também estender a máquina de estados. Aqui está um exemplo.
@ 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 );
...
}
A anotação pode ser definida tanto na classe de implementação da máquina de estados quanto em qualquer interface em que a máquina de estados será implementada. Também pode ser usado misturado com API fluente, o que significa que a máquina de estados definida na API fluente também pode ser estendida por essas anotações. (Uma coisa que você precisa observar é que o método definido na interface deve ser público, o que significa que também a implementação da ação de chamada do método será pública para o chamador.)
Conversores
Para declarar estado e evento dentro de @State e @Transit , o usuário precisa implementar conversores correspondentes para seu tipo de estado (S) e evento (E). A conversão deve implementar a interface Converter<T>, que converte o estado/evento de/para String.
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 );
}
Em seguida, registre esses conversores em ConverterProvider . por exemplo
ConverterProvider . INSTANCE . register ( MyEvent . class , new MyEventConverter ());
ConverterProvider . INSTANCE . register ( MyState . class , new MyStateConverter ());
Nota: Se você usar apenas API fluente para definir a máquina de estado, não há necessidade de implementar conversores correspondentes. E também se a classe Event ou State for do tipo String ou Enumeration, você não precisa implementar ou registrar um conversor explicitamente na maioria dos casos.
Nova instância de máquina de estado
Após o comportamento da máquina de estado definido pelo usuário, o usuário poderá criar uma nova instância da máquina de estado por meio do construtor. Observe que, uma vez que a instância da máquina de estado é criada a partir do construtor, o construtor não pode mais ser usado para definir qualquer novo elemento da máquina de estado.
T newStateMachine ( S initialStateId , Object ... extraParams );
Para criar uma nova instância de máquina de estado a partir do construtor de máquina de estado, você precisa passar os seguintes parâmetros.
initialStateId
: quando iniciado, o estado inicial da máquina de estado.
extraParams
: Parâmetros extras necessários para criar uma nova instância de máquina de estado. Defina como "new Object[0]" para que nenhum parâmetro extra seja necessário.
um. Se o usuário passou parâmetros extras ao criar uma nova instância de máquina de estado, certifique-se de que StateMachineBuilderFactory também tenha definido o tipo de parâmetros extras ao criar o construtor de máquina de estado. Caso contrário, o parâmetro extra será ignorado. b. Parâmetros extras podem ser passados para a instância da máquina de estado de duas maneiras. Uma é através do construtor da máquina de estado, o que significa que o usuário precisa definir um construtor com o mesmo tipo e ordem de parâmetros para a instância da máquina de estado. Outra forma é definir um método chamado postConstruct
e também com o mesmo tipo e ordem de parâmetros.
Se nenhum parâmetro extra precisar ser passado para a máquina de estado, o usuário pode simplesmente chamar T newStateMachine(S initialStateId)
para criar uma nova instância de máquina de estado.
Nova máquina de estado do construtor de máquinas de estado. (Neste caso, nenhum parâmetro extra precisa ser passado.)
MyStateMachine stateMachine = builder . newStateMachine ( MyState . Initial );
Transições de gatilho
Após a criação da máquina de estado, o usuário pode disparar eventos junto com o contexto para acionar a transição dentro da máquina de estado. por exemplo
stateMachine . fire ( MyEvent . Prepare , new MyContext ( "Testing" ));
Máquina de estado não digitada
Para simplificar o uso da máquina de estado e evitar muitos tipos genéricos (por exemplo, StateMachine<T, S, E, C>) que podem dificultar a leitura do código em alguns casos, mas ainda mantêm parte importante do recurso de segurança de tipo na ação de transição execução, UntypedStateMachine foi implementado para este propósito.
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" );
Para construir um UntypedStateMachine, o usuário precisa primeiro criar um UntypedStateMachineBuilder por meio de StateMachineBuilderFactory. StateMachineBuilderFactory usa apenas um parâmetro que é o tipo de classe de máquina de estado para criar UntypedStateMachineBuilder. @StateMachineParameters é usado para declarar tipos de parâmetros genéricos de máquinas de estado. AbstractUntypedStateMachine é a classe base de qualquer máquina de estado não tipada.
Máquina de estado insensível ao contexto
Às vezes, a transição de estado não se preocupa com o contexto, o que significa que a transição é determinada principalmente apenas pelo evento. Para este caso, o usuário pode usar a máquina de estado insensível ao contexto para simplificar os parâmetros de chamada de método. Declarar uma máquina de estado insensível ao contexto é bastante simples. O usuário só precisa adicionar a anotação @ContextInsensitive na classe de implementação da máquina de estado. Depois disso, o parâmetro de contexto pode ser ignorado na lista de parâmetros do método de transição. por exemplo
@ 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 ) {
...
}
}
Tratamento de exceções de transição
Quando uma exceção ocorre durante a transição de estado, a lista de ações executadas será abortada e a máquina de estado entrará no status de erro, o que significa que a instância da máquina de estado não poderá mais processar o evento. Se o usuário continuar a disparar eventos para a instância da máquina de estado, uma IllegalStateException será lançada. Todas as exceções ocorridas durante a fase de transição, incluindo a execução da ação e a invocação do ouvinte externo, serão agrupadas em TransitionException (exceção não verificada). Atualmente, a estratégia padrão de tratamento de exceções é simples e rude, apenas continuando a descartar a exceção, consulte o método AbstractStateMachine.afterTransitionCausedException.
protected void afterTransitionCausedException (...) { throw e ; }
Se a máquina de estado puder ser recuperada desta exceção, o usuário poderá estender o método afterTransitionCausedException e adicionar a lógica de recuperação correspondente neste método. NÃO se esqueça de definir o status da máquina de estado de volta ao normal no final. por exemplo
@ 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 );
}
}
Definir estado hierárquico
Um estado hierárquico pode conter um estado aninhado. Os próprios estados filhos podem ter filhos aninhados e o aninhamento pode prosseguir em qualquer profundidade. Quando um estado hierárquico está ativo, apenas um de seus estados filhos está ativo. O estado hierárquico pode ser definido através de API ou anotação.
void defineSequentialStatesOn ( S parentStateId , S ... childStateIds );
builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA) define dois estados filhos "BinA" e "CinA" no estado pai "A", o primeiro estado filho definido também será o estado inicial do estado hierárquico "A" . O mesmo estado hierárquico também pode ser definido através de anotação, por exemplo
@ 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" )
})
Definir estado paralelo
O estado paralelo encapsula um conjunto de estados filhos que estão simultaneamente ativos quando o elemento pai está ativo. O estado paralelo pode ser definido através de API ou anotação. por exemplo
// 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 );
ou
@ 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" )
})
Para obter subestados atuais do estado paralelo
stateMachine . getSubStatesOn ( MyState . Root ); // return list of current sub states of parallel state
Quando todos os estados paralelos atingirem o estado final, um evento de contexto Finish será acionado.
Definir Evento de Contexto
Evento de contexto significa que o evento definido pelo usuário possui um contexto predefinido na máquina de estado. squirrel-foundation definiu três tipos de eventos de contexto para diferentes casos de uso. Evento de início/término : O evento declarado como evento de início/término será usado quando a máquina de estado for iniciada/terminada. Assim, o usuário pode diferenciar o gatilho da ação invocada, por exemplo, quando a máquina de estado está iniciando e entrando em seu estado inicial, o usuário pode diferenciar se essa ação de entrada de estado foi invocada pelo evento de início. Evento de término : quando todos os estados paralelos atingirem o estado final, o evento de término será disparado automaticamente. O usuário pode definir a transição seguinte com base no evento final. Para definir o evento de contexto, o usuário possui API bidirecional, de anotação ou de construtor.
@ ContextEvent ( finishEvent = "Finish" )
static class ParallelStateMachine extends AbstractStateMachine <...> {
}
ou
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (...);
...
builder . defineFinishEvent ( HEvent . Start );
builder . defineTerminateEvent ( HEvent . Terminate );
builder . defineStartEvent ( HEvent . Finish );
Usando estados históricos para salvar e restaurar o estado atual
O pseudoestado histórico permite que uma máquina de estado lembre sua configuração de estado. Uma transição tomando o estado histórico como destino retornará a máquina de estado a esta configuração registrada. Se o 'tipo' de um histórico for "superficial", o processador da máquina de estado deverá registrar os filhos ativos diretos de seu pai antes de realizar qualquer transição que saia do pai. Se o 'tipo' de um histórico for "profundo", o processador da máquina de estado deverá registrar todos os descendentes ativos do pai antes de realizar qualquer transição que saia do pai. Tanto a API quanto a anotação são suportadas para definir o tipo de estado do histórico. por exemplo
// defined history type of state "A" as "deep"
builder . defineSequentialStatesOn ( MyState . A , HistoryType . DEEP , MyState . A1 , MyState . A2 )
ou
@ State ( parent = "A" , name = "A1" , entryCallMethod = "enterA1" , exitCallMethod = "exitA1" , historyType = HistoryType . DEEP )
Nota: Antes da versão 0.3.7, o usuário precisa definir "HistoryType.DEEP" para cada nível de estado histórico, o que não é muito conveniente. (Obrigado a Voskuijlen por fornecer a solução Issue33). Agora o usuário define apenas "HistoryType.DEEP" no nível superior do estado histórico, e todas as informações históricas do estado filho serão lembradas.
Tipos de transição
De acordo com a especificação UML, uma transição pode ser de um destes três tipos:
- Transição Interna Implica que a Transição, se acionada, ocorre sem sair ou entrar no Estado de origem (ou seja, não causa mudança de estado). Isto significa que a condição de entrada ou saída do Estado fonte não será invocada. Uma transição interna pode ser realizada mesmo se StateMachine estiver em uma ou mais regiões aninhadas no estado associado.
- Transição Local Implica que a Transição, se acionada, não sairá do Estado composto (fonte), mas sairá e entrará novamente em qualquer estado dentro do Estado composto que esteja na configuração do estado atual.
- Transição Externa Implica que a Transição, se acionada, sairá do Estado composto (fonte)
squirrel-foundation suporta API e anotação para declarar todos os tipos de transições, por exemplo
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 )
ou
@ 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 ),
})
Envio de eventos de polimorfismo
Durante o ciclo de vida da máquina de estados, vários eventos serão disparados, por exemplo
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 */
O usuário pode adicionar um ouvinte para escutar StateMachineEvent, o que significa que todos os eventos disparados durante o ciclo de vida da máquina de estado serão capturados por este ouvinte, por exemplo,
stateMachine . addStateMachineListener ( new StateMachineListener <...>() {
@ Override
public void stateMachineEvent ( StateMachineEvent <...> event ) {
// ...
}
});
E o usuário também pode adicionar um ouvinte para ouvir TransitionEvent por meio de StateMachine.addTransitionListener, o que significa que todos os eventos disparados durante cada transição de estado, incluindo TransitionBeginEvent, TransitionCompleteEvent e TransitionEndEvent, serão capturados por este ouvinte. Ou o usuário pode adicionar um ouvinte específico, por exemplo, TransitionDeclinedListener para ouvir TransitionDeclinedEvent quando a solicitação de transição for recusada.
Ouvinte de evento declarativo
Adicionar o ouvinte de evento acima à máquina de estado às vezes é irritante para o usuário e muitos tipos genéricos também tornam o código feio de ler. Para simplificar o uso da máquina de estado, mais importante para fornecer uma integração não invasiva, o squirrel-foundation fornece uma maneira declarativa de adicionar um ouvinte de evento por meio da seguinte anotação, por exemplo
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 );
Ao fazer esse código de módulo externo, não é necessário implementar nenhuma interface de ouvinte de máquina de estado. Adicione apenas algumas anotações nos métodos que serão conectados durante a fase de transição. Os parâmetros do método também são de tipo seguro e serão inferidos automaticamente para corresponder ao evento correspondente. Esta é uma boa abordagem para separação de preocupações . O usuário pode encontrar exemplos de uso em org.squirrelframework.foundation.fsm.StateMachineLogger .
Métodos de extensão de transição
Cada evento de transição também possui um método de extensão correspondente na classe AbstractStateMachine que pode ser estendido na classe de implementação da máquina de estado do cliente.
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 ) {
}
Normalmente, o usuário pode conectar sua lógica de processamento de negócios nesses métodos de extensão durante cada transição de estado, enquanto os vários ouvintes de eventos servem como limite do sistema de controle baseado em máquina de estado, que pode interagir com módulos externos (por exemplo, UI, Auditoria, ESB e assim por diante ). Por exemplo, o usuário pode estender o método afterTransitionCausedException para limpeza do ambiente quando uma exceção ocorreu durante a transição e também notificar o módulo de interface do usuário para exibir uma mensagem de erro por meio de TransitionExceptionEvent.
Ação Ponderada
O usuário pode definir o peso da ação para ajustar a ordem de execução da ação. As ações durante a entrada/saída de estado e transição de estado são ordenadas em ordem crescente de acordo com seu valor de peso. O peso da ação é 0 por padrão. O usuário tem duas maneiras de definir o peso da ação.
Um deles é anexar o número do peso ao nome do método e separar por ':'.
// 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" )
Outra maneira é substituir o método de peso da classe Action, por exemplo
Action <...> newAction = new Action <...>() {
...
@ Override
public int weight () {
return 100 ;
}
}
esquilo-fundação também apóia uma maneira convencional de declarar o peso da ação. O peso da ação de chamada de método cujo nome começa com ' antes ' será definido como 100, assim como o nome iniciado com ' depois ' será definido como -100. Geralmente significa que o nome do método de ação iniciado com 'antes' será invocado primeiro, enquanto o nome do método de ação iniciado com 'depois' será invocado por último. "method1:ignore" significa que o método1 não será invocado.
Para obter mais informações, consulte o caso de teste ' org.squirrelframework.foundation.fsm.WeightedActionTest ';
Execução Assincronizada
A anotação @AsyncExecute pode ser usada na ação de chamada de método e no ouvinte de evento declarativo para indicar que esta ação ou ouvinte de evento será executado de forma assíncrona, por exemplo, Definir método de ação invocado de forma assíncrona:
@ 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
}
}
Defina evento despachado de forma assíncrona:
public class DeclarativeListener {
@ OnTransitionBegin
@ AsyncExecute
public void onTransitionBegin (...) {
// transition begin event will be dispatched asynchronously to this listener method
}
}
A tarefa de execução assíncrona será enviada para um ExecutorService . O usuário pode registrar sua instância de implementação ExecutorService através de SquirrelSingletonProvider , por exemplo
ExecutorService executorService = Executors . newFixedThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ExecutorService . class , executorService );
Se nenhuma instância do ExecutorService foi registrada, o SquirrelConfiguration fornecerá uma instância padrão.
Pós-processador de máquina de estado
O usuário pode registrar o pós-processador para um tipo específico de máquina de estado para adicionar lógica de pós-processamento após a máquina de estado instanciada, por exemplo
// 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 );
Para este caso, quando o usuário criou as instâncias MyStateMachineImpl e MyStateMachineImplEx, o pós-processador registrado MyStateMachinePostProcessor será chamado para realizar algum trabalho.
Exportação de máquina de estado
SCXMLVisitor pode ser usado para exportar a definição da máquina de estado no documento [SCXML] 2.
SCXMLVisitor visitor = SquirrelProvider . getInstance (). newInstance ( SCXMLVisitor . class );
stateMachine . accept ( visitor );
visitor . convertSCXMLFile ( "MyStateMachine" , true );
BTW, o usuário também pode chamar StateMachine.exportXMLDefinition(true) para exportar a definição XML embelezada. O DotVisitor pode ser usado para gerar um diagrama de estado que pode ser visualizado pelo [GraphViz] 3.
DotVisitor visitor = SquirrelProvider . getInstance (). newInstance ( DotVisitor . class );
stateMachine . accept ( visitor );
visitor . convertDotFile ( "SnakeStateMachine" );
Importação de máquina de estado
UntypedStateMachineImporter pode ser usado para importar uma definição semelhante a SCXML da máquina de estado que foi exportada por SCXMLVisitor ou definição de manuscrito. UntypedStateMachineImporter construirá um UntypedStateMachineBuilder de acordo com a definição que pode ser usada posteriormente para criar instâncias de máquina de estado.
UntypedStateMachineBuilder builder = new UntypedStateMachineImporter (). importDefinition ( scxmlDef );
ATMStateMachine stateMachine = builder . newAnyStateMachine ( ATMState . Idle );
Nota: O UntypedStateMachineImporter forneceu um estilo XML para definir a máquina de estado, assim como a API do construtor de máquina de estado ou anotações declarativas. A definição semelhante ao SCXML não é igual ao SCXML padrão.
Salvar/carregar dados de máquina de estado
O usuário pode salvar dados da máquina de estado quando a máquina de estado estiver ociosa.
StateMachineData . Reader < MyStateMachine , MyState , MyEvent , MyContext >
savedData = stateMachine . dumpSavedData ();
E também o usuário pode carregar os dados salvos acima em outra máquina de estado cujo status foi encerrado ou apenas inicializado.
newStateMachineInstance . loadSavedData ( savedData );
NOTA : Os dados da máquina de estado podem ser serializados/desserializados a partir de uma string codificada em Base64 com a ajuda da classe ObjectSerializableSupport .
Configuração da máquina de estado
Ao criar uma nova instância de máquina de estado, o usuário pode configurar seu comportamento através de StateMachineConfiguration , por exemplo
UntypedStateMachine fsm = builder . newUntypedStateMachine ( "a" ,
StateMachineConfiguration . create (). enableAutoStart ( false )
. setIdProvider ( IdProvider . UUIDProvider . getInstance ()),
new Object [ 0 ]); // since 0.3.0
fsm . fire ( TestEvent . toA );
O código de exemplo acima é usado para criar uma instância de máquina de estado com UUID como identificador e desabilitar a função de inicialização automática. StateMachineConfigure também pode ser definido no construtor de máquina de estado, o que significa que todas as instâncias de máquina de estado criadas por builder.newStateMachine(S initialStateId)
ou builder.newStateMachine(S initialStateId, Object... extraParams)
usarão esta configuração.
Diagnóstico de máquina de estado
StateMachineLogger é usado para observar o status interno da máquina de estado, como o desempenho de execução, sequência de chamada de ação, progresso de transição e assim por diante, por exemplo
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 .
...
Como o registrador de máquina de estado v0.3.0 pode ser usado de maneira mais fácil, basta definir StateMachineConfiguration para ativar o modo de depuração, por exemplo
StateMachine<?,?,?,?> stateMachine = builder.newStateMachine(HState.A,
StateMachineConfiguration.create().enableDebugMode(true),
new Object[0]);
StateMachinePerformanceMonitor pode ser usado para monitorar informações de desempenho de execução da máquina de estado, incluindo contagem total de tempos de transição, tempo médio consumido pela transição e assim por diante, por exemplo
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 ==========================
Adicionar @LogExecTime no método de ação desconectará o tempo de execução do método. E também adicionar o @LogExecTime na classe da máquina de estado irá desconectar todo o tempo de execução do método de ação. Por exemplo, o tempo de execução do método transitFromAToBOnGoToB será desconectado.
@ LogExecTime
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
Estado cronometrado
Um estado cronometrado é um estado que pode atrasar ou acionar periodicamente um evento especificado após a entrada do estado. A tarefa cronometrada será enviada para um ScheduledExecutorService . O usuário pode registrar sua instância de implementação ScheduledExecutorService através de SquirrelSingletonProvider , por exemplo
ScheduledExecutorService scheduler = Executors . newScheduledThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ScheduledExecutorService . class , scheduler );
Se nenhuma instância ScheduledExecutorService foi registrada, SquirrelConfiguration fornecerá uma instância padrão. Depois disso, um estado cronometrado pode ser definido pelo construtor da máquina de estado, por exemplo
// after 50ms delay fire event "FIRST" every 100ms with null context
builder . defineTimedState ( "A" , 50 , 100 , "FIRST" , null );
builder . internalTransition (). within ( "A" ). on ( "FIRST" );
NOTA : Certifique-se de que o estado cronometrado deve ser definido antes de descrever suas transições ou ações de entrada/saída. timeInterval menor ou igual a 0 será considerado executado apenas uma vez após inicialDelay .
Estado vinculado (chamado estado de submáquina)
Um estado vinculado especifica a inserção da especificação de uma máquina de estado de submáquina. A máquina de estado que contém o estado vinculado é chamada de máquina de estado que contém. A mesma máquina de estados pode ser uma submáquina mais de uma vez no contexto de uma única máquina de estados que contém.
Um estado vinculado é semanticamente equivalente a um estado composto. As regiões da máquina de estado da submáquina são as regiões do estado composto. As ações de entrada, saída e comportamento e transições internas são definidas como parte do estado. O estado da submáquina é um mecanismo de decomposição que permite a fatoração de comportamentos comuns e sua reutilização. O estado vinculado pode ser definido seguindo o código de exemplo.
builderOfTestStateMachine . definedLinkedState ( LState . A , builderOfLinkedStateMachine , LState . A1 );
Suporte JMX
Desde 0.3.3, o usuário pode monitorar remotamente a instância da máquina de estado (por exemplo, status atual, nome) e modificar configurações (por exemplo, alternar registros/alternar monitor de desempenho/evento de incêndio remoto) em tempo de execução. Todas as informações das instâncias da máquina de estado estarão no domínio "org.squirrelframework". O código de amostra a seguir mostra como ativar o suporte JMX.
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (...);
builder . setStateMachineConfiguration ( StateMachineConfiguration . create (). enableRemoteMonitor ( true ));
NOTA : O suporte ao recurso JMX foi descontinuado desde 0.3.9-SNAPSHOT.
Veja arquivo EXEMPLOS.
Consulte o arquivo NOTAS DE LANÇAMENTO.
Para as últimas atualizações, siga meu twitter @hhe11 ou +HeHenry
Para discussões ou perguntas, junte-se ao grupo de máquinas de estado de esquilo
Para qualquer problema ou requisito, envie um problema
Se você usar o código Squirrel State Machine em seu aplicativo, agradecerei se você informar o autor sobre isso (e-mail: [email protected]) assim :
Assunto: Texto de notificação de uso da máquina de estado do esquilo: Eu uso a máquina de estado do esquilo <lib_version> em <project_name> - http://link_to_project. Eu [permito | não permita] mencionar meu projeto na seção "Quem usa Squirrel State Machine" no GitHub.