It is a little limited to do interaction with a state machine by either listening its events or using actions with states and transitions. Time to time this approach would be too limited and verbose to create interaction with the application a state machine is working with. For this specific use case we have made a spring style context integration which easily attach state machine functionality into your beans.
Available annotations has been harmonised to enable access to same state machine execution points than what is available from Chapter 20, Listening State Machine Events.
@WithStateMachine annotation can be used to associate a state machine with an existing bean. Then it is possible to start adding supported annotations to methods of that bean.
@WithStateMachine public class Bean1 { @OnTransition public void anyTransition() { } }
It is also possible to attach to any other state machine from an
application context by using annotation name
field.
@WithStateMachine(name = "myMachineBeanName") public class Bean2 { @OnTransition public void anyTransition() { } }
Sometimes it is more convenient to use machine id which is something user can set to better identify multiple instances. This id maps to getId() method in a StateMachine interface.
@WithStateMachine(id = "myMachineId") public class Bean16 { @OnTransition public void anyTransition() { } }
@WithStateMachine can also be used as a meta-annotation as shown above. In this case you could annotate your bean with WithMyBean.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @WithStateMachine(name = "myMachineBeanName") public @interface WithMyBean { }
Note | |
---|---|
Return type of these methods doesn’t matter and is effectively discard. |
All features for @WithStateMachine can be enabled by using annotation @EnableWithStateMachine which simply imports needed configuration into Spring Application Context. Both @EnableStateMachine and @EnableStateMachineFactory are already annotated with this so there is no need for user to add it again. However if machine is build and configured without a use of configuration adapters, @EnableWithStateMachine must be used order to use features with @WithStateMachine. Idea for this is shown below.
public static StateMachine<String, String> buildMachine(BeanFactory beanFactory) throws Exception { Builder<String, String> builder = StateMachineBuilder.builder(); builder.configureConfiguration() .withConfiguration() .machineId("myMachineId") .beanFactory(beanFactory); builder.configureStates() .withStates() .initial("S1") .state("S2"); builder.configureTransitions() .withExternal() .source("S1") .target("S2") .event("E1"); return builder.build(); } @WithStateMachine(id = "myMachineId") static class Bean17 { @OnStateChanged public void onStateChanged() { } }
Important | |
---|---|
If machine is not created as a Bean then it is mandatory to set BeanFactory for a machine as shown above. Otherwise machine will be unaware of handlers calling your @WithStateMachine methods. |
Every annotation is supporting exactly same set of possible method parameters but runtime behaviour is different depending on an annotation itself and a stage where annotated method is called. To better understand how context works see Chapter 18, Using StateContext.
Note | |
---|---|
For differences between method parameters, see individual annotation docs below. |
Effectively all annotated methods are called using Spring SPel
expressions which are build dynamically during the process. As to make
this work these expressions needs to have a root object it evaluates
against. This root object is a StateContext
and we have also made some
tweaks internally so that it is possible to access StateContext
methods
directly without going through the context handle.
Simplest method parameter would naturally be a StateContext
itself.
@WithStateMachine public class Bean3 { @OnTransition public void anyTransition(StateContext<String, String> stateContext) { } }
Rest of the StateContext
content can be accessed as shown below.
Number of parameters or order of those doesn’t matter.
@WithStateMachine public class Bean4 { @OnTransition public void anyTransition( @EventHeaders Map<String, Object> headers, ExtendedState extendedState, StateMachine<String, String> stateMachine, Message<String> message, Exception e) { } }
Annotations for transitions are OnTransition
, OnTransitionStart
and OnTransitionEnd
.
These annotations behave exactly same and let’s
see how OnTransition
is used. Within this annotation a property’s
source and target can be used to qualify a transition. If
source and target is left empty then any transition is matched.
@WithStateMachine public class Bean5 { @OnTransition(source = "S1", target = "S2") public void fromS1ToS2() { } @OnTransition public void anyTransition() { } }
Default @OnTransition annotation can’t be used with a state and event enums user have created due to java language limitations, thus string representation have to be used.
Additionally it is possible to access Event Headers
and
ExtendedState
by adding needed arguments to a method. Method
is then called automatically with these arguments.
@WithStateMachine public class Bean6 { @StatesOnTransition(source = States.S1, target = States.S2) public void fromS1ToS2(@EventHeaders Map<String, Object> headers, ExtendedState extendedState) { } }
However if you want to have a type safe annotation it is possible to create a new annotation and use @OnTransition as meta annotation. This user level annotation can make a reference to actual states and events enums and framework will try to match these in a same way.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @OnTransition public @interface StatesOnTransition { States[] source() default {}; States[] target() default {}; }
Above we created a @StatesOnTransition annotation which defines
source
and target
as a type safe manner.
@WithStateMachine public class Bean7 { @StatesOnTransition(source = States.S1, target = States.S2) public void fromS1ToS2() { } }
In your own bean you can then use this @StatesOnTransition as is and
use type safe source
and target
.
Annotations for states are OnStateChanged
, OnStateEntry
and
OnStateExit
.
@WithStateMachine public class Bean8 { @OnStateChanged public void anyStateChange() { } }
In a same way that in transition annotations it’s possible to define target and source states.
@WithStateMachine public class Bean9 { @OnStateChanged(source = "S1", target = "S2") public void stateChangeFromS1toS2() { } }
For type safety a new annotation needs to be created for enums with
OnStateChanged
as a meta annotation.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @OnStateChanged public @interface StatesOnStates { States[] source() default {}; States[] target() default {}; }
@WithStateMachine public class Bean10 { @StatesOnStates(source = States.S1, target = States.S2) public void fromS1ToS2() { } }
Methods for state entry and exit behave in a same way.
@WithStateMachine public class Bean11 { @OnStateEntry public void anyStateEntry() { } @OnStateExit public void anyStateExit() { } }
There is one event related annotation named OnEventNotAccepted
. It
is possible to listen only specific event by defining event
property
with the annotation.
@WithStateMachine public class Bean12 { @OnEventNotAccepted public void anyEventNotAccepted() { } @OnEventNotAccepted(event = "E1") public void e1EventNotAccepted() { } }
Annotations for state machine are OnStateMachineStart
,
OnStateMachineStop
and OnStateMachineError
.
During a state machine start and stop lifecycle methods are called.
@WithStateMachine public class Bean13 { @OnStateMachineStart public void onStateMachineStart() { } @OnStateMachineStop public void onStateMachineStop() { } }
In case a state machine goes into an error with exception, below annotation is called.
@WithStateMachine public class Bean14 { @OnStateMachineError public void onStateMachineError() { } }
There is one extended state related annotation named
OnExtendedStateChanged
. It’s also possible to listen changes only
for specific key
changes.
@WithStateMachine public class Bean15 { @OnExtendedStateChanged public void anyStateChange() { } @OnExtendedStateChanged(key = "key1") public void key1Changed() { } }