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 19, 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() { } }
@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. |
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 17, 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 lets
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 anotations 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() { } }