Spring’s recent release of their state machine framework, dubbed Statemachine version 1.1, offers a host of new features including:
- Support for Spring Security
- Enhanced integration with @WithStateMachine
- Built-in support for Redis
- Support for UI modeling
According to the Spring Statemachine website, Spring Statemachine “is a framework for application developers to use state machine concepts with Spring applications.” Statemachine includes all of the usual core Spring framework components such as associating beans to a Finite State Machine (FSM) through Spring’s Inversion of Control.
Deploying an FSM in an application is useful where there are a fixed number of states and events that transition from one state to the next. Statemachine uses triggers based on events or timers to make those transitions.
States and events can be implemented using two data types: String and Enumeration.
As an example, let’s develop a detailed turnstile, with the following state diagram:
In this turnstile FSM, the states are LOCKED and UNLOCKED, the events are InsertToken and PassThru, and the actions are Unlock, Lock, Alarm, and Refund. The initial state is LOCKED as denoted in the diagram by the inner concentric circle within that state. Initiating the InsertToken event triggers an Unlock action and changes the state from LOCKED to UNLOCKED. Once in the UNLOCKED state, initiating the PassThru event triggers the Lock action and the state of the turnstile is transitioned back to the LOCKED state. The actions, Alarm and Refund, do not cause a change in state.
To keep the demo application simple, actions will not be defined. States and events will be implemented using Enumeration:
static enum States {
LOCKED,
UNLOCKED
}
static enum Events {
INSERTTOKEN,
PASSTHRU
}
The next task is to configure the states and transitions through by defining a Spring @Configuration annotation context in our StateMachineConfigurer interface:
@Configuration
@EnableStateMachine
static class Config extends EnumStateMachineConfigurerAdapter<States,Events> {
@Override
public void configure(StateMachineStateConfigurer<States,Events> states)
throws Exception {
states
.withStates()
.initial(States.LOCKED)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States,Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.LOCKED)
.target(States.UNLOCKED)
.event(Events.InsertToken)
.and()
.withExternal()
.source(States.UNLOCKED)
.target(States.LOCKED)
.event(Events.PassThru);
}
}
The @EnableStateMachine annotation signals this context that the turnstile will immediately be built and started.
In this configuration, the initial state, LOCKED, is defined with the statement, states.withStates().initial(States.LOCKED). Transitions are defined using the methods source(), target(), and event() as shown above. This series of methods should be defined for all state transitions in an FSM. The method withExternal() is the standard way of changing states. The method withInternal() would be used if an action does not require a change in state as with Alarm and Refund shown in the diagram.
A StateMachineListener is defined to monitor the turnstile FSM progress:
static class StateMachineListener extends StateMachineListenerAdapter<States,Events> {
@Override
public void stateChanged(State<States,Events> from,State<States,Events> to) {
System.out.println("State changed to: " + to.getId());
}
}
Finally, an instance of StateMachine is created and a run() method is defined to instantiate the listener, start the turnstile FSM, and send events.
@Autowired
StateMachine<States,Events> stateMachine;
@Override
public void run(String... args) throws Exception {
StateMachineListener listener = new StateMachineListener();
stateMachine.addStateListener(listener);
stateMachine.start();
stateMachine.sendEvent(Events.InsertToken);
stateMachine.sendEvent(Events.PassThru);
}
public static void main(String[] args) {
SpringApplication.run(org.redlich.statemachine.DemoApplication.class,args);
}
The entire project can be found on Github. A detailed reference guide can be found on the Spring Statemachine website.