There are use cases where you just want to know what is happening with a state machine, react to something or simply get logging for debugging purposes. SSM provides interfaces for adding listeners which then gives an option to get callback when various state changes, actions, etc are happening.
You basically have two options, either to listen Spring application context events or directly attach listener to a state machine. Both of these basically will provide same information where one is producing events as event classes and other producing callbacks via a listener interface. Both of these have pros and cons which will be discussed later.
Application context events classes are OnTransitionStartEvent, OnTransitionEvent, OnTransitionEndEvent, OnStateExitEvent, OnStateEntryEvent, OnStateChangedEvent, OnStateMachineStart and OnStateMachineStop and others which extends base event class StateMachineEvent These can be used as is with spring typed ApplicationListener.
StateMachine will send context events via StateMachineEventPublisher it’s set. Default implementation is automatically created if @Configuration class is annotated with @EnableStateMachine.
public class StateMachineApplicationEventListener implements ApplicationListener<StateMachineEvent> { @Override public void onApplicationEvent(StateMachineEvent event) { } } @Configuration public class ListenerConfig { @Bean public StateMachineApplicationEventListener contextListener() { return new StateMachineApplicationEventListener(); } }
Context events are also automatically enabled via @EnableStateMachine with machine builder StateMachine registered as a bean as shown below.
@Configuration @EnableStateMachine public class ManualBuilderConfig { @Bean public StateMachine<String, String> stateMachine() throws Exception { Builder<String, String> builder = StateMachineBuilder.builder(); builder.configureStates() .withStates() .initial("S1") .state("S2"); builder.configureTransitions() .withExternal() .source("S1") .target("S2") .event("E1"); return builder.build(); } }
Using StateMachineListener you can either extend it and implement all callback methods or use StateMachineListenerAdapter class which contains stub method implementations and choose which ones to override.
public class StateMachineEventListener extends StateMachineListenerAdapter<States, Events> { @Override public void stateChanged(State<States, Events> from, State<States, Events> to) { } @Override public void stateEntered(State<States, Events> state) { } @Override public void stateExited(State<States, Events> state) { } @Override public void transition(Transition<States, Events> transition) { } @Override public void transitionStarted(Transition<States, Events> transition) { } @Override public void transitionEnded(Transition<States, Events> transition) { } @Override public void stateMachineStarted(StateMachine<States, Events> stateMachine) { } @Override public void stateMachineStopped(StateMachine<States, Events> stateMachine) { } @Override public void eventNotAccepted(Message<Events> event) { } @Override public void extendedStateChanged(Object key, Object value) { } @Override public void stateMachineError(StateMachine<States, Events> stateMachine, Exception exception) { } @Override public void stateContext(StateContext<States, Events> stateContext) { } }
In above example we simply created our own listener class StateMachineEventListener which extends StateMachineListenerAdapter.
Listener method stateContext
gives an access to various
StateContext changes on a different stages. More about about it in
section Chapter 18, Using StateContext.
Once you have your own listener defined, it can be registered into a state machine via its interface as shown below. It’s just a matter of flavour if it’s hooked up within a spring configuration or done manually at any time of application life-cycle.
public class Config7 { @Autowired StateMachine<States, Events> stateMachine; @Bean public StateMachineEventListener stateMachineEventListener() { StateMachineEventListener listener = new StateMachineEventListener(); stateMachine.addStateListener(listener); return listener; } }
Spring application context is not a fastest eventbus out there so it
is advised to give some thought what is a rate of events state machine
is sending. For better performance it may be better to use
StateMachineListener interface. For this specific reason it is
possible to use contextEvents
flag with @EnableStateMachine and
@EnableStateMachineFactory to disable Spring application context
events as shown above.
@Configuration @EnableStateMachine(contextEvents = false) public class Config8 extends EnumStateMachineConfigurerAdapter<States, Events> { } @Configuration @EnableStateMachineFactory(contextEvents = false) public class Config9 extends EnumStateMachineConfigurerAdapter<States, Events> { }