Using Spring Statemachine

This part of the reference documentation explains the core functionality that Spring Statemachine provides to any Spring based application.spring-doc.cn

It includes the following topics:spring-doc.cn

Statemachine Configuration

One of the common tasks when using a state machine is to design its runtime configuration. This chapter focuses on how Spring Statemachine is configured and how it leverages Spring’s lightweight IoC containers to simplify the application internals to make it more manageable.spring-doc.cn

Configuration examples in this section are not feature complete. That is, you always need to have definitions of both states and transitions. Otherwise, state machine configuration would be ill-formed. We have simply made code snippets less verbose by leaving other needed parts out.

Using enable Annotations

We use two familiar Spring enabler annotations to ease configuration: @EnableStateMachine and @EnableStateMachineFactory. These annotations, when placed in a @Configuration class, enable some basic functionality needed by a state machine.spring-doc.cn

You can use @EnableStateMachine when you need a configuration to create an instance of StateMachine. Usually, a @Configuration class extends adapters (EnumStateMachineConfigurerAdapter or StateMachineConfigurerAdapter), which lets you override configuration callback methods. We automatically detect whether you use these adapter classes and modify the runtime configuration logic accordingly.spring-doc.cn

You can use @EnableStateMachineFactory when you need a configuration to create an instance of a StateMachineFactory.spring-doc.cn

Usage examples of these are shown in below sections.

Configuring States

We get into more complex configuration examples a bit later in this guide, but we first start with something simple. For most simple state machine, you can use EnumStateMachineConfigurerAdapter and define possible states and choose the initial and optional end states.spring-doc.cn

@Configuration
@EnableStateMachine
public class Config1Enums
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1)
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

}

You can also use strings instead of enumerations as states and events by using StateMachineConfigurerAdapter, as shown in the next example. Most of the configuration examples ues enumerations, but, generally speaking, you can interchange strings and enumerations.spring-doc.cn

@Configuration
@EnableStateMachine
public class Config1Strings
		extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
			.withStates()
				.initial("S1")
				.end("SF")
				.states(new HashSet<String>(Arrays.asList("S1","S2","S3","S4")));
	}

}
Using enumerations brings a safer set of states and event types but limits possible combinations to compile time. Strings do not have this limitation and let you use more dynamic ways to build state machine configurations but do not allow same level of safety.

Configuring Hierarchical States

You can define hierarchical states can by using multiple withStates() calls, where you can use parent() to indicate that these particular states are sub-states of some other state. The following example shows how to do so:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config2
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1)
				.state(States.S1)
				.and()
				.withStates()
					.parent(States.S1)
					.initial(States.S2)
					.state(States.S2);
	}

}

Configuring Regions

There are no special configuration methods to mark a collection of states to be part of an orthogonal state. To put it simply, orthogonal state is created when the same hierarchical state machine has multiple sets of states, each of which has an initial state. Because an individual state machine can only have one initial state, multiple initial states must mean that a specific state must have multiple independent regions. The following example shows how to define regions:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config10
		extends EnumStateMachineConfigurerAdapter<States2, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States2, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States2.S1)
				.state(States2.S2)
				.and()
				.withStates()
					.parent(States2.S2)
					.initial(States2.S2I)
					.state(States2.S21)
					.end(States2.S2F)
					.and()
				.withStates()
					.parent(States2.S2)
					.initial(States2.S3I)
					.state(States2.S31)
					.end(States2.S3F);
	}

}

When persisting machines with regions or generally relying on any functionalities to reset a machine, you may need to have a dedicated ID for a region. By default, this ID is a generated UUID. As the following example shows, StateConfigurer has a method called region(String id) that lets you set the ID for a region:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config10RegionId
		extends EnumStateMachineConfigurerAdapter<States2, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States2, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States2.S1)
				.state(States2.S2)
				.and()
				.withStates()
					.parent(States2.S2)
					.region("R1")
					.initial(States2.S2I)
					.state(States2.S21)
					.end(States2.S2F)
					.and()
				.withStates()
					.parent(States2.S2)
					.region("R2")
					.initial(States2.S3I)
					.state(States2.S31)
					.end(States2.S3F);
	}

}

Configuring Transitions

We support three different types of transitions: external, internal, and local. Transitions are triggered either by a signal (which is an event sent into a state machine) or by a timer. The following example shows how to define all three kinds of transitions:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config3
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1)
				.states(EnumSet.allOf(States.class));
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source(States.S1).target(States.S2)
				.event(Events.E1)
				.and()
			.withInternal()
				.source(States.S2)
				.event(Events.E2)
				.and()
			.withLocal()
				.source(States.S2).target(States.S3)
				.event(Events.E3);
	}

}

Configuring Guards

You can use guards to protect state transitions. You can use the Guard interface to do an evaluation where a method has access to a StateContext. The following example shows how to do so:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config4
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source(States.S1).target(States.S2)
				.event(Events.E1)
				.guard(guard())
				.and()
			.withExternal()
				.source(States.S2).target(States.S3)
				.event(Events.E2)
				.guardExpression("true");

	}

	@Bean
	public Guard<States, Events> guard() {
		return new Guard<States, Events>() {

			@Override
			public boolean evaluate(StateContext<States, Events> context) {
				return true;
			}
		};
	}

}

In the preceding example, we used two different types of guard configurations. First, we created a simple Guard as a bean and attached it to the transition between states S1 and S2.spring-doc.cn

Second, we used a SPeL expression as a guard to dicate that the expression must return a BOOLEAN value. Behind the scenes, this expression-based guard is a SpelExpressionGuard. We attached it to the transition between states S2 and S3. Both guards always evaluate to true.spring-doc.cn

Configuring Actions

You can define actions to be executed with transitions and states. An action is always run as a result of a transition that originates from a trigger. The following example shows how to define an action:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config51
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source(States.S1)
				.target(States.S2)
				.event(Events.E1)
				.action(action());
	}

	@Bean
	public Action<States, Events> action() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// do something
			}
		};
	}

}

In the preceding example, a single Action is defined as a bean named action and associated with a transition from S1 to S2. The following example shows how to use an action multiple times:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config52
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1, action())
				.state(States.S1, action(), null)
				.state(States.S2, null, action())
				.state(States.S2, action())
				.state(States.S3, action(), action());
	}

	@Bean
	public Action<States, Events> action() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// do something
			}
		};
	}

}
Usually, you would not define the same Action instance for different stages, but we did it here to not make too much noise in a code snippet.

In the preceding example, a single Action is defined by the bean named action and associated with states S1, S2, and S3. We need to clarify what is going on here:spring-doc.cn

  • We defined an action for the initial state, S1.spring-doc.cn

  • We defined an entry action for state S1 and left the exit action empty.spring-doc.cn

  • We defined an exit action for state S2 and left the entry action empty.spring-doc.cn

  • We defined a single state action for state S2.spring-doc.cn

  • We defined both entry and exit actions for state S3.spring-doc.cn

  • Note that state S1 is used twice with initial() and state() functions. You need to do this only if you want to define entry or exit actions with initial state.spring-doc.cn

Defining action with initial() function only runs a particular action when a state machine or sub state is started. This action is an initializing action that is run only once. An action defined with state() is then run if the state machine transitions back and forward between initial and non-initial states.

State Actions

State actions are run differently compared to entry and exit actions, because execution happens after state has been entered and can be cancelled if state exit happens before a particular action has been completed.spring-doc.cn

State actions are executed using normal reactive flow by subscribing with a Reactor’s default parallel scheduler. This means that, whatever you do in your action, you need to be able to catch InterruptedException or, more generally, periodically check whether Thread is interrupted.spring-doc.cn

The following example shows typical configuration that uses default the IMMEDIATE_CANCEL, which would immediately cancel a running task when its state is complete:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config1 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
		config
			.withConfiguration()
				.stateDoActionPolicy(StateDoActionPolicy.IMMEDIATE_CANCEL);
	}

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
		states
			.withStates()
				.initial("S1")
				.state("S2", context -> {})
				.state("S3");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
		transitions
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1")
				.and()
			.withExternal()
				.source("S2")
				.target("S3")
				.event("E2");
	}
}

You can set a policy to TIMEOUT_CANCEL together with a global timeout for each machine. This changes state behavior to await action completion before cancelation is requested. The following example shows how to do so:spring-doc.cn

@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
	config
		.withConfiguration()
			.stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL)
			.stateDoActionPolicyTimeout(10, TimeUnit.SECONDS);
}

If Event directly takes a machine into a state so that event headers are available to a particular action, you can also use a dedicated event header to set a specific timeout (defined in millis). You can use the reserved header value StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT for this purpose. The following example shows how to do so:spring-doc.cn

@Autowired
StateMachine<String, String> stateMachine;

void sendEventUsingTimeout() {
	stateMachine
		.sendEvent(Mono.just(MessageBuilder
			.withPayload("E1")
			.setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000)
			.build()))
		.subscribe();

}

Transition Action Error Handling

You can always catch exceptions manually. However, with actions defined for transitions, you can define an error action that is called if an exception is raised. The exception is then available from a StateContext passed to that action. The following example shows how to create a state that handles an exception:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config53
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source(States.S1)
				.target(States.S2)
				.event(Events.E1)
				.action(action(), errorAction());
	}

	@Bean
	public Action<States, Events> action() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				throw new RuntimeException("MyError");
			}
		};
	}

	@Bean
	public Action<States, Events> errorAction() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// RuntimeException("MyError") added to context
				Exception exception = context.getException();
				exception.getMessage();
			}
		};
	}

}

If need be, you can manually create similar logic for every action. The following example shows how to do so:spring-doc.cn

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
		throws Exception {
	transitions
		.withExternal()
			.source(States.S1)
			.target(States.S2)
			.event(Events.E1)
			.action(Actions.errorCallingAction(action(), errorAction()));
}

State Action Error Handling

Logic similar to the logic that handles errors in state transitions is also available for entry to a state and exit from a state.spring-doc.cn

For these situations, StateConfigurer has methods called stateEntry, stateDo, and stateExit. These methods define an error action together with a normal (non-error) action. The following example shows how to use all three methods:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config55
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1)
				.stateEntry(States.S2, action(), errorAction())
				.stateDo(States.S2, action(), errorAction())
				.stateExit(States.S2, action(), errorAction())
				.state(States.S3);
	}

	@Bean
	public Action<States, Events> action() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				throw new RuntimeException("MyError");
			}
		};
	}

	@Bean
	public Action<States, Events> errorAction() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// RuntimeException("MyError") added to context
				Exception exception = context.getException();
				exception.getMessage();
			}
		};
	}
}

Configuring Pseudo States

Pseudo state configuration is usually done by configuring states and transitions. Pseudo states are automatically added to state machine as states.spring-doc.cn

Initial State

You can mark a particular state as initial state by using the initial() method. This initial action is good, for example, to initialize extended state variables. The following example shows how to use the initial() method:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config11
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1, initialAction())
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

	@Bean
	public Action<States, Events> initialAction() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// do something initially
			}
		};
	}

}

Terminate State

You can mark a particular state as being an end state by using the end() method. You can do so at most once for each individual sub-machine or region. The following example shows how to use the end() method:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config1Enums
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1)
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

}

State History

You can define state history once for each individual state machine. You need to choose its state identifier and set either History.SHALLOW or History.DEEP. The following example uses History.SHALLOW:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config12
		extends EnumStateMachineConfigurerAdapter<States3, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States3, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States3.S1)
				.state(States3.S2)
				.and()
				.withStates()
					.parent(States3.S2)
					.initial(States3.S2I)
					.state(States3.S21)
					.state(States3.S22)
					.history(States3.SH, History.SHALLOW);
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States3, Events> transitions)
			throws Exception {
		transitions
			.withHistory()
				.source(States3.SH)
				.target(States3.S22);
	}

}

Also, as the preceding example shows, you can optionally define a default transition from a history state into a state vertex in a same machine. This transition takes place as a default if, for example, the machine has never been entered — thus, no history would be available. If a default state transition is not defined, then normal entry into a region is done. This default transition is also used if a machine’s history is a final state.spring-doc.cn

Choice State

Choice needs to be defined in both states and transitions to work properly. You can mark a particular state as being a choice state by using the choice() method. This state needs to match source state when a transition is configured for this choice.spring-doc.cn

You can configure a transition by using withChoice(), where you define source state and a first/then/last structure, which is equivalent to a normal if/elseif/else. With first and then, you can specify a guard just as you would use a condition with if/elseif clauses.spring-doc.cn

A transition needs to be able to exist, so you must make sure to use last. Otherwise, the configuration is ill-formed. The following example shows how to define a choice state:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config13
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.SI)
				.choice(States.S1)
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withChoice()
				.source(States.S1)
				.first(States.S2, s2Guard())
				.then(States.S3, s3Guard())
				.last(States.S4);
	}

	@Bean
	public Guard<States, Events> s2Guard() {
		return new Guard<States, Events>() {

			@Override
			public boolean evaluate(StateContext<States, Events> context) {
				return false;
			}
		};
	}

	@Bean
	public Guard<States, Events> s3Guard() {
		return new Guard<States, Events>() {

			@Override
			public boolean evaluate(StateContext<States, Events> context) {
				return true;
			}
		};
	}

}

Actions can be run with both incoming and outgoing transitions of a choice pseudostate. As the following example shows, one dummy lambda action is defined that leads into a choice state and one similar dummy lambda action is defined for one outgoing transition (where it also defines an error action):spring-doc.cn

@Configuration
@EnableStateMachine
public class Config23
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.SI)
				.choice(States.S1)
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source(States.SI)
				.action(c -> {
						// action with SI-S1
					})
				.target(States.S1)
				.and()
			.withChoice()
				.source(States.S1)
				.first(States.S2, c -> {
						return true;
					})
				.last(States.S3, c -> {
						// action with S1-S3
					}, c -> {
						// error callback for action S1-S3
					});
	}
}
Junction have same api format meaning actions can be defined similarly.

Junction State

You need to define a junction in both states and transitions for it to work properly. You can mark a particular state as being a choice state by using the junction() method. This state needs to match the source state when a transition is configured for this choice.spring-doc.cn

You can configure the transition by using withJunction() where you define source state and a first/then/last structure (which is equivalent to a normal if/elseif/else). With first and then, you can specify a guard as you would use a condition with if/elseif clauses.spring-doc.cn

A transition needs to be able to exist, so you must make sure to use last. Otherwise, the configuration is ill-formed. The following example uses a junction:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config20
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.SI)
				.junction(States.S1)
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
			throws Exception {
		transitions
			.withJunction()
				.source(States.S1)
				.first(States.S2, s2Guard())
				.then(States.S3, s3Guard())
				.last(States.S4);
	}

	@Bean
	public Guard<States, Events> s2Guard() {
		return new Guard<States, Events>() {

			@Override
			public boolean evaluate(StateContext<States, Events> context) {
				return false;
			}
		};
	}

	@Bean
	public Guard<States, Events> s3Guard() {
		return new Guard<States, Events>() {

			@Override
			public boolean evaluate(StateContext<States, Events> context) {
				return true;
			}
		};
	}

}
The difference between choice and junction is purely academic, as both are implemented with first/then/last structures . However, in theory, based on UML modeling, choice allows only one incoming transition while junction allows multiple incoming transitions. At a code level, the functionality is pretty much identical.

Fork State

You must define a fork in both states and transitions for it to work properly. You can mark a particular state as being a choice state by using the fork() method. This state needs to match source state when a transition is configured for this fork.spring-doc.cn

The target state needs to be a super state or an immediate state in a regions. Using a super state as a target takes all regions into initial states. Targeting individual state gives more controlled entry into regions. The following example uses a fork:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config14
		extends EnumStateMachineConfigurerAdapter<States2, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States2, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States2.S1)
				.fork(States2.S2)
				.state(States2.S3)
				.and()
				.withStates()
					.parent(States2.S3)
					.initial(States2.S2I)
					.state(States2.S21)
					.state(States2.S22)
					.end(States2.S2F)
					.and()
				.withStates()
					.parent(States2.S3)
					.initial(States2.S3I)
					.state(States2.S31)
					.state(States2.S32)
					.end(States2.S3F);
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States2, Events> transitions)
			throws Exception {
		transitions
			.withFork()
				.source(States2.S2)
				.target(States2.S22)
				.target(States2.S32);
	}

}

Join State

You must define a join in both states and transitions for it to work properly. You can mark aparticular state as being a choice state by using the join() method. This state does not need to match either source states or a target state in a transition configuration.spring-doc.cn

You can select a target state where a transition goes when all source states have been joined. If you use state hosting regions as the source, the end states of a region are used as joins. Otherwise, you can pick any states from a region. The following exmaple uses a join:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config15
		extends EnumStateMachineConfigurerAdapter<States2, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States2, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States2.S1)
				.state(States2.S3)
				.join(States2.S4)
				.state(States2.S5)
				.and()
				.withStates()
					.parent(States2.S3)
					.initial(States2.S2I)
					.state(States2.S21)
					.state(States2.S22)
					.end(States2.S2F)
					.and()
				.withStates()
					.parent(States2.S3)
					.initial(States2.S3I)
					.state(States2.S31)
					.state(States2.S32)
					.end(States2.S3F);
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States2, Events> transitions)
			throws Exception {
		transitions
			.withJoin()
				.source(States2.S2F)
				.source(States2.S3F)
				.target(States2.S4)
				.and()
			.withExternal()
				.source(States2.S4)
				.target(States2.S5);
	}
}

You can also have multiple transitions originate from a join state. It this case, we advise you to use guards and define your guards such that only one guard evaluates to TRUE at any given time. Otherwise, transition behavior is not predictable. This is shown in the following example, where the guard checks whether the extended state has variables:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config22
		extends EnumStateMachineConfigurerAdapter<States2, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States2, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States2.S1)
				.state(States2.S3)
				.join(States2.S4)
				.state(States2.S5)
				.end(States2.SF)
				.and()
				.withStates()
					.parent(States2.S3)
					.initial(States2.S2I)
					.state(States2.S21)
					.state(States2.S22)
					.end(States2.S2F)
					.and()
				.withStates()
					.parent(States2.S3)
					.initial(States2.S3I)
					.state(States2.S31)
					.state(States2.S32)
					.end(States2.S3F);
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<States2, Events> transitions)
			throws Exception {
		transitions
			.withJoin()
				.source(States2.S2F)
				.source(States2.S3F)
				.target(States2.S4)
				.and()
			.withExternal()
				.source(States2.S4)
				.target(States2.S5)
				.guardExpression("!extendedState.variables.isEmpty()")
				.and()
			.withExternal()
				.source(States2.S4)
				.target(States2.SF)
				.guardExpression("extendedState.variables.isEmpty()");
	}
}

Exit and Entry Point States

You can use exit and entry points to do more controlled exit and entry from and into a submachine. The following example uses the withEntry and withExit methods to define entry points:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config21 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
		.withStates()
			.initial("S1")
			.state("S2")
			.state("S3")
			.and()
			.withStates()
				.parent("S2")
				.initial("S21")
				.entry("S2ENTRY")
				.exit("S2EXIT")
				.state("S22");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
		.withExternal()
			.source("S1").target("S2")
			.event("E1")
			.and()
		.withExternal()
			.source("S1").target("S2ENTRY")
			.event("ENTRY")
			.and()
		.withExternal()
			.source("S22").target("S2EXIT")
			.event("EXIT")
			.and()
		.withEntry()
			.source("S2ENTRY").target("S22")
			.and()
		.withExit()
			.source("S2EXIT").target("S3");
	}
}

As shown in the preceding, you need to mark particular states as being exit and entry states. Then you create a normal transitions into those states and also specify withExit() and withEntry(), where those states exit and entry respectively.spring-doc.cn

Configuring Common Settings

You can set part of a common state machine configuration by using ConfigurationConfigurer. With it you can set BeanFactory and an autostart flag for a state machine. It also lets you register StateMachineListener instances, configure transition conflict policy and region execution policy. The following example shows how to use ConfigurationConfigurer:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config17
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<States, Events> config)
			throws Exception {
		config
			.withConfiguration()
				.autoStartup(true)
				.machineId("myMachineId")
				.beanFactory(new StaticListableBeanFactory())
				.listener(new StateMachineListenerAdapter<States, Events>())
				.transitionConflictPolicy(TransitionConflictPolicy.CHILD)
				.regionExecutionPolicy(RegionExecutionPolicy.PARALLEL);
	}
}

By default, the state machine autoStartup flag is disabled, because all instances that handle sub-states are controlled by the state machine itself and cannot be automatically started. Also, it is much safer to leave whether a machine should be started automatically or not to the user. This flag controls only the autostart of a top-level state machine.spring-doc.cn

Setting machineId within a configuration class is simply a convenience for those times when you want or need to do it there.spring-doc.cn

Registering StateMachineListener instances is also partly for convenience but is required if you want to catch a callback during a state machine lifecycle, such as getting notified of a state machine’s start and stop events. Note that you cannot listen a state machine’s start events if autoStartup is enabled, unless you register a listener during a configuration phase.spring-doc.cn

You can use transitionConflictPolicy when multiple transition paths could be selected. One usual use case for this is when a machine contains anonymous transitions that lead out from a sub-state and a parent state and you want to define a policy in which one is selected. This is a global setting within a machine instance and defaults to CHILD.spring-doc.cn

You can use withDistributed() to configure DistributedStateMachine. It lets you set a StateMachineEnsemble, which (if it exists) automatically wraps any created StateMachine with DistributedStateMachine and enables distributed mode. The following example shows how to use it:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config18
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<States, Events> config)
			throws Exception {
		config
			.withDistributed()
				.ensemble(stateMachineEnsemble());
	}

	@Bean
	public StateMachineEnsemble<States, Events> stateMachineEnsemble()
			throws Exception {
		// naturally not null but should return ensemble instance
		return null;
	}
}

For more about distributed states, see Using Distributed States.spring-doc.cn

The StateMachineModelVerifier interface is used internally to do some sanity checks for a state machine’s structure. Its purpose is to fail fast early instead of letting common configuration errors into a state machine. By default, a verifier is automatically enabled and the DefaultStateMachineModelVerifier implementation is used.spring-doc.cn

With withVerifier(), you can disable verifier or set a custom one if needed. The following example shows how to do so:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config19
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<States, Events> config)
			throws Exception {
		config
			.withVerifier()
				.enabled(true)
				.verifier(verifier());
	}

	@Bean
	public StateMachineModelVerifier<States, Events> verifier() {
		return new StateMachineModelVerifier<States, Events>() {

			@Override
			public void verify(StateMachineModel<States, Events> model) {
				// throw exception indicating malformed model
			}
		};
	}
}

For more about config model, see StateMachine Config Model.spring-doc.cn

The withSecurity, withMonitoring and withPersistence configuration methods are documented in State Machine Security, Monitoring a State Machine, and Using StateMachineRuntimePersister, respectively.

Configuring Model

StateMachineModelFactory is a hook that lets you configure a statemachine model without using a manual configuration. Essentially, it is a third-party integration to integrate into a configuration model. You can hook StateMachineModelFactory into a configuration model by using a StateMachineModelConfigurer. The following example shows how to do so:spring-doc.cn

@Configuration
@EnableStateMachine
public static class Config1 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
		model
			.withModel()
				.factory(modelFactory());
	}

	@Bean
	public StateMachineModelFactory<String, String> modelFactory() {
		return new CustomStateMachineModelFactory();
	}
}

The follwoing example uses CustomStateMachineModelFactory to define two states (S1 and S2) and an event (E1) between those states:spring-doc.cn

public static class CustomStateMachineModelFactory implements StateMachineModelFactory<String, String> {

	@Override
	public StateMachineModel<String, String> build() {
		ConfigurationData<String, String> configurationData = new ConfigurationData<>();
		Collection<StateData<String, String>> stateData = new ArrayList<>();
		stateData.add(new StateData<String, String>("S1", true));
		stateData.add(new StateData<String, String>("S2"));
		StatesData<String, String> statesData = new StatesData<>(stateData);
		Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
		transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
		TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);
		StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<String, String>(configurationData,
				statesData, transitionsData);
		return stateMachineModel;
	}

	@Override
	public StateMachineModel<String, String> build(String machineId) {
		return build();
	}
}
Defining a custom model is usually not what people are looking for, although it is possible. However, it is a central concept of allowing external access to this configuration model.

You can find an example of using this model factory integration in Eclipse Modeling Support. You can find more generic info about custom model integration in Developer Documentation.spring-doc.cn

Things to Remember

When defining actions, guards, or any other references from a configuration, it pays to remember how Spring Framework works with beans. In the next example, we have defined a normal configuration with states S1 and S2 and four transitions between those. All transitions are guarded by either guard1 or guard2. You must ensure that guard1 is created as a real bean because it is annotated with @Bean, while guard2 is not.spring-doc.cn

This means that event E3 would get the guard2 condition as TRUE, and E4 would get the guard2 condition as FALSE, because those are coming from plain method calls to those functions.spring-doc.cn

However, because guard1 is defined as a @Bean, it is proxied by the Spring Framework. Thus, additional calls to its method result in only one instantiation of that instance. Event E1 would first get the proxied instance with condition TRUE, while event E2 would get the same instance with TRUE condition when the method call was defined with FALSE. This is not a Spring State Machine-specific behavior. Rather, it is how Spring Framework works with beans. The following example shows how this arrangement works:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config1
		extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
			.withStates()
				.initial("S1")
				.state("S2");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("S1").target("S2").event("E1").guard(guard1(true))
				.and()
			.withExternal()
				.source("S1").target("S2").event("E2").guard(guard1(false))
				.and()
			.withExternal()
				.source("S1").target("S2").event("E3").guard(guard2(true))
				.and()
			.withExternal()
				.source("S1").target("S2").event("E4").guard(guard2(false));
	}

	@Bean
	public Guard<String, String> guard1(final boolean value) {
		return new Guard<String, String>() {
			@Override
			public boolean evaluate(StateContext<String, String> context) {
				return value;
			}
		};
	}

	public Guard<String, String> guard2(final boolean value) {
		return new Guard<String, String>() {
			@Override
			public boolean evaluate(StateContext<String, String> context) {
				return value;
			}
		};
	}
}

State Machine ID

Various classes and interfaces use machineId either as a variable or as a parameter in methods. This section takes a closer look at how machineId relates to normal machine operation and instantiation.spring-doc.cn

During runtime, a machineId really does not have any big operational role except to distinguish machines from each other — for example, when following logs or doing deeper debugging. Having a lot of different machine instances quickly gets developers lost in translation if there is no easy way to identify these instances. As a result, we added the option to set the machineId.spring-doc.cn

Using @EnableStateMachine

Setting machineId in Java configuration as mymachine then exposes that value for logs. This same machineId is also available from the StateMachine.getId() method. The following example uses the machineId method:spring-doc.cn

@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
		throws Exception {
	config
		.withConfiguration()
			.machineId("mymachine");
}

The following example of log output shows the mymachine ID:spring-doc.cn

11:23:54,509  INFO main support.LifecycleObjectSupport [main] -
started S2 S1  / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine
The manual builder (see State Machine through a Builder) uses the same configuration interface, meaning that the behavior is equivalent.

Using @EnableStateMachineFactory

You can see the same machineId getting configured if you use a StateMachineFactory and request a new machine by using that ID, as the following example shows:spring-doc.cn

StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
StateMachine<String, String> machine = factory.getStateMachine("mymachine");

Using StateMachineModelFactory

Behind the scenes, all machine configurations are first translated into a StateMachineModel so that StateMachineFactory need not know from where the configuration originated, as a machine can be built from Java configuration, UML, or a repository. If you want to go crazy, you can also use a custom StateMachineModel, which is the lowest possible level at which to define configuration.spring-doc.cn

What do all of these have to do with a machineId? StateMachineModelFactory also has a method with the following signature: StateMachineModel<S, E> build(String machineId) which a StateMachineModelFactory implementation may choose to use.spring-doc.cn

RepositoryStateMachineModelFactory (see Repository Support) uses machineId to support different configurations in a persistent store through Spring Data Repository interfaces. For example, both StateRepository and TransitionRepository have a method (List<T> findByMachineId(String machineId)), to build different states and transitions by a machineId. With RepositoryStateMachineModelFactory, if machineId is used as empty or NULL, it defaults to repository configuration (in a backing-persistent model) without a known machine id.spring-doc.cn

Currently, UmlStateMachineModelFactory does not distinguish between different machine IDs, as UML source is always coming from the same file. This may change in future releases.

State Machine Factories

There are use cases when a state machine needs to be created dynamically instead of by defining static configuration at compile time. For example, if there are custom components that use their own state machines and these components are created dynamically, it is impossible to have a static state machine that is built during the application start. Internally, state machines are always built through factory interfaces. This then gives you an option to use this feature programmatically. Configuration for a state machine factory is exactly the same as shown in various examples in this document where state machine configuration is hard coded.spring-doc.cn

Factory through an Adapter

Actually creating a state machine by using @EnableStateMachine works through a factory, so @EnableStateMachineFactory merely exposes that factory through its interface. The following example uses @EnableStateMachineFactory:spring-doc.cn

@Configuration
@EnableStateMachineFactory
public class Config6
		extends EnumStateMachineConfigurerAdapter<States, Events> {

	@Override
	public void configure(StateMachineStateConfigurer<States, Events> states)
			throws Exception {
		states
			.withStates()
				.initial(States.S1)
				.end(States.SF)
				.states(EnumSet.allOf(States.class));
	}

}

Now that you have used @EnableStateMachineFactory to create a factory instead of a state machine bean, you can inject it and use it (as is) to request new state machines. The following example shows how to do so:spring-doc.cn

public class Bean3 {

	@Autowired
	StateMachineFactory<States, Events> factory;

	void method() {
		StateMachine<States,Events> stateMachine = factory.getStateMachine();
		stateMachine.startReactively().subscribe();
	}
}

Adapter Factory Limitations

The current limitation of factory is that all the actions and guard with which it associates a state machine share the same instance. This means that, from your actions and guard, you need to specifically handle the case in which the same bean is called by different state machines. This limitation is something that will be resolved in future releases.spring-doc.cn

State Machine through a Builder

Using adapters (as shown above) has a limitation imposed by its requirement to work through Spring @Configuration classes and the application context. While this is a very clear model to configure a state machine, it limits configuration at compile time, which is not always what a user wants to do. If there is a requirement to build more dynamic state machines, you can use a simple builder pattern to construct similar instances. By using strings as states and events, you can use this builder pattern to build fully dynamic state machines outside of a Spring application context. The following example shows how to do so:spring-doc.cn

StateMachine<String, String> buildMachine1() throws Exception {
	Builder<String, String> builder = StateMachineBuilder.builder();
	builder.configureStates()
		.withStates()
			.initial("S1")
			.end("SF")
			.states(new HashSet<String>(Arrays.asList("S1","S2","S3","S4")));
	return builder.build();
}

The builder uses the same configuration interfaces behind the scenes that the @Configuration model uses for adapter classes. The same model goes to configuring transitions, states, and common configuration through a builder’s methods. This means that whatever you can use with a normal EnumStateMachineConfigurerAdapter or StateMachineConfigurerAdapter you can use dynamically through a builder.spring-doc.cn

Currently, the builder.configureStates(), builder.configureTransitions(), and builder.configureConfiguration() interface methods cannot be chained together, meaning that builder methods need to be called individually.

The following example sets a number of options with a builder:spring-doc.cn

StateMachine<String, String> buildMachine2() throws Exception {
	Builder<String, String> builder = StateMachineBuilder.builder();
	builder.configureConfiguration()
		.withConfiguration()
			.autoStartup(false)
			.beanFactory(null)
			.listener(null);
	return builder.build();
}

You need to understand when common configuration needs to be used with machines instantiated from a builder. You can use a configurer returned from a withConfiguration() to setup autoStart and BeanFactory. You can also use one to register a StateMachineListener. If a StateMachine instance returned from a builder is registered as a bean by using @Bean, BeanFactory is attached automatically. If you use instances outside of a spring application context, you must use these methods to set up the needed facilities.spring-doc.cn

Using Deferred Events

When an event is sent, it may fire an EventTrigger, which may then cause a transition to happen, if a state machine is in a state where a trigger is evaluated successfully. Normally, this may lead to a situation where an event is not accepted and is dropped. However, you may wish postpone this event until a state machine enters another state. In that case, you can accept that event. In other words, an event arrives at an inconvenient time.spring-doc.cn

Spring Statemachine provides a mechanism for deferring events for later processing. Every state can have a list of deferred events. If an event in the current state’s deferred event list occurs, the event is saved (deferred) for future processing until a state is entered that does not list the event in its deferred event list. When such a state is entered, the state machine automatically recalls any saved events that are no longer deferred and then either consumes or discards these events. It is possible for a superstate to have a transition defined on an event that is deferred by a substate. Following same hierarchical state machines concepts, the substate takes precedence over the superstate, the event is deferred, and the transition for the superstate is not run. With orthogonal regions, where one orthogonal region defers an event and another accepts the event, the accept takes precedence and the event is consumed and not deferred.spring-doc.cn

The most obvious use case for event deferring is when an event causes a transition into a particular state and the state machine is then returned back to its original state where a second event should cause the same transition. The following example shows this situation:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config5 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
			.withStates()
				.initial("READY")
				.state("DEPLOYPREPARE", "DEPLOY")
				.state("DEPLOYEXECUTE", "DEPLOY");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("READY").target("DEPLOYPREPARE")
				.event("DEPLOY")
				.and()
			.withExternal()
				.source("DEPLOYPREPARE").target("DEPLOYEXECUTE")
				.and()
			.withExternal()
				.source("DEPLOYEXECUTE").target("READY");
	}
}

In the preceding example, the state machine has a state of READY, which indicates that the machine is ready to process events that would take it into a DEPLOY state, where the actual deployment would happen. After a deploy action has been run, the machine is returned back to the READY state. Sending multiple events in a READY state does not cause any trouble if the machine is using synchronous executors, because event sending would block between event calls. However, if the executor uses threads, other events may get lost, because the machine is no longer in a state where events can be processed. Thus, deferring some of these events lets the machine preserve them. The following example shows how to configure such an arrangement:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config6 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
			.withStates()
				.initial("READY")
				.state("DEPLOY", "DEPLOY")
				.state("DONE")
				.and()
				.withStates()
					.parent("DEPLOY")
					.initial("DEPLOYPREPARE")
					.state("DEPLOYPREPARE", "DONE")
					.state("DEPLOYEXECUTE");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("READY").target("DEPLOY")
				.event("DEPLOY")
				.and()
			.withExternal()
				.source("DEPLOYPREPARE").target("DEPLOYEXECUTE")
				.and()
			.withExternal()
				.source("DEPLOYEXECUTE").target("READY")
				.and()
			.withExternal()
				.source("READY").target("DONE")
				.event("DONE")
				.and()
			.withExternal()
				.source("DEPLOY").target("DONE")
				.event("DONE");
	}
}

In the preceding example, the state machine uses nested states instead of a flat state model, so the DEPLOY event can be deferred directly in a substate. It also shows the concept of deferring the DONE event in a sub-state that would then override the anonymous transition between the DEPLOY and DONE states if the state machine happens to be in a DEPLOYPREPARE state when the DONE event is dispatched. In the DEPLOYEXECUTE state when the DONE event is not deferred, this event would be handled in a super state.spring-doc.cn

Using Scopes

Support for scopes in a state machine is very limited, but you can enable session scope by using a normal Spring @Scope annotation in one of two ways:spring-doc.cn

  • If the state machine is built manually by using a builder and returned into the context as a @Bean.spring-doc.cn

  • Through a configuration adapter.spring-doc.cn

Both of these need @Scope to be present, with scopeName set to session and proxyMode set to ScopedProxyMode.TARGET_CLASS. The following examples show both use cases:spring-doc.cn

@Configuration
public class Config3 {

	@Bean
	@Scope(scopeName="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
	StateMachine<String, String> stateMachine() throws Exception {
		Builder<String, String> builder = StateMachineBuilder.builder();
		builder.configureConfiguration()
			.withConfiguration()
				.autoStartup(true);
		builder.configureStates()
			.withStates()
				.initial("S1")
				.state("S2");
		builder.configureTransitions()
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1");
		StateMachine<String, String> stateMachine = builder.build();
		return stateMachine;
	}

}
@Configuration
@EnableStateMachine
@Scope(scopeName="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public static class Config4 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
		config
			.withConfiguration()
				.autoStartup(true);
	}

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
		states
			.withStates()
				.initial("S1")
				.state("S2");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
		transitions
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1");
	}

}

TIP:See Scope for how to use session scoping.spring-doc.cn

Once you have scoped a state machine into session, autowiring it into a @Controller gives a new state machine instance per session. Each state machine is then destroyed when HttpSession is invalidated. The following example shows how to use a state machine in a controller:spring-doc.cn

@Controller
public class StateMachineController {

	@Autowired
	StateMachine<String, String> stateMachine;

	@RequestMapping(path="/state", method=RequestMethod.POST)
	public HttpEntity<Void> setState(@RequestParam("event") String event) {
		stateMachine
			.sendEvent(Mono.just(MessageBuilder
				.withPayload(event).build()))
			.subscribe();
		return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
	}

	@RequestMapping(path="/state", method=RequestMethod.GET)
	@ResponseBody
	public String getState() {
		return stateMachine.getState().getId();
	}
}
Using state machines in a session scopes needs careful planning, mostly because it is a relatively heavy component.
Spring Statemachine poms have no dependencies to Spring MVC classes, which you will need to work with session scope. However, if you are working with a web application, you have already pulled those dependencies directly from Spring MVC or Spring Boot.

Using Actions

Actions are one of the most useful components that you can use to interact and collaborate with a state machine. You can run actions in various places in a state machine and its states lifecycle — for example, entering or exiting states or during transitions. The following example shows how to use actions in a state machine:spring-doc.cn

@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
		throws Exception {
	states
		.withStates()
			.initial(States.SI)
			.state(States.S1, action1(), action2())
			.state(States.S2, action1(), action2())
			.state(States.S3, action1(), action3());
}

In the preceding example, the action1 and action2 beans are attached to the entry and exit states, respectively. The following example defines those actions (and action3):spring-doc.cn

@Bean
public Action<States, Events> action1() {
	return new Action<States, Events>() {

		@Override
		public void execute(StateContext<States, Events> context) {
		}
	};
}

@Bean
public BaseAction action2() {
	return new BaseAction();
}

@Bean
public SpelAction action3() {
	ExpressionParser parser = new SpelExpressionParser();
	return new SpelAction(
			parser.parseExpression(
					"stateMachine.sendEvent(T(org.springframework.statemachine.docs.Events).E1)"));
}

public class BaseAction implements Action<States, Events> {

	@Override
	public void execute(StateContext<States, Events> context) {
	}
}

public class SpelAction extends SpelExpressionAction<States, Events> {

	public SpelAction(Expression expression) {
		super(expression);
	}
}

You can directly implement Action as an anonymous function or create your own implementation and define the appropriate implementation as a bean.spring-doc.cn

In the preceding example, action3 uses a SpEL expression to send the Events.E1 event into a state machine.spring-doc.cn

StateContext is described in Using StateContext.

SpEL Expressions with Actions

You can also use a SpEL expression as a replacement for a full Action implementation.spring-doc.cn

Reactive Actions

Normal Action interface is a simple functional method taking StateContext and returning void. There’s nothing blocking here until you block in a method itself and this is a bit of a problem as framework cannot know what’s exactly happening inside of it.spring-doc.cn

public interface Action<S, E> {
	void execute(StateContext<S, E> context);
}

To overcome this issue we’ve internally changed Action handling to process a plain java’s Function taking StateContext and returning Mono. This way we can call action and fully in a reactive way to execute action only when it’s subscribed and in a non-blocking way to wait completion.spring-doc.cn

public interface ReactiveAction<S, E> extends Function<StateContext<S, E>, Mono<Void>> {
}

Internally old Action interface is wrapped with a Reactor Mono Runnable as it shares same return type. We have no control what you do in that method!spring-doc.cn

Using Guards

As shown in Things to Remember, the guard1 and guard2 beans are attached to the entry and exit states, respectively. The following example also uses guards on events:spring-doc.cn

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
		throws Exception {
	transitions
		.withExternal()
			.source(States.SI).target(States.S1)
			.event(Events.E1)
			.guard(guard1())
			.and()
		.withExternal()
			.source(States.S1).target(States.S2)
			.event(Events.E1)
			.guard(guard2())
			.and()
		.withExternal()
			.source(States.S2).target(States.S3)
			.event(Events.E2)
			.guardExpression("extendedState.variables.get('myvar')");
}

You can directly implement Guard as an anonymous function or create your own implementation and define the appropriate implementation as a bean. In the preceding example, guardExpression checkS whether the extended state variable named myvar evaluates to TRUE. The following example implements some sample guards:spring-doc.cn

@Bean
public Guard<States, Events> guard1() {
	return new Guard<States, Events>() {

		@Override
		public boolean evaluate(StateContext<States, Events> context) {
			return true;
		}
	};
}

@Bean
public BaseGuard guard2() {
	return new BaseGuard();
}

public class BaseGuard implements Guard<States, Events> {

	@Override
	public boolean evaluate(StateContext<States, Events> context) {
		return false;
	}
}
StateContext is described in section Using StateContext.

SpEL Expressions with Guards

You can also use a SpEL expression as a replacement for a full Guard implementation. The only requirement is that the expression needs to return a Boolean value to satisfy the Guard implementation. This can be demonstrated with a guardExpression() function that takes an expression as an argument.spring-doc.cn

Reactive Guards

Normal Guard interface is a simple functional method taking StateContext and returning boolean. There’s nothing blocking here until you block in a method itself and this is a bit of a problem as framework cannot know what’s exactly happening inside of it.spring-doc.cn

public interface Guard<S, E> {
	boolean evaluate(StateContext<S, E> context);
}

To overcome this issue we’ve internally changed Guard handling to process a plain java’s Function taking StateContext and returning Mono<Boolean>. This way we can call guard and fully in a reactive way to evaluate it only when it’s subscribed and in a non-blocking way to wait completion with a return value.spring-doc.cn

public interface ReactiveGuard<S, E> extends Function<StateContext<S, E>, Mono<Boolean>> {
}

Internally old Guard interface is wrapped with a Reactor Mono Function. We have no control what you do in that method!spring-doc.cn

Using Extended State

Assume that you need to create a state machine that tracks how many times a user is pressing a key on a keyboard and then terminates when keys are pressed 1000 times. A possible but really naive solution would be to create a new state for each 1000 key presses. You might suddenly have an astronomical number of states, which, naturally, is not very practical.spring-doc.cn

This is where extended state variables come to the rescue by not needing to add more states to drive state machine changes. Instead, you can do a simple variable change during a transition.spring-doc.cn

StateMachine has a method called getExtendedState(). It returns an interface called ExtendedState, which gives access to extended state variables. You can access these variables directly through a state machine or through StateContext during a callback from actions or transitions. The following example shows how to do so:spring-doc.cn

public Action<String, String> myVariableAction() {
	return new Action<String, String>() {

		@Override
		public void execute(StateContext<String, String> context) {
			context.getExtendedState()
				.getVariables().put("mykey", "myvalue");
		}
	};
}

If you need to get notified for extended state variable changes, you have two options: either use StateMachineListener or listen for extendedStateChanged(key, value) callbacks. The following example uses the extendedStateChanged method:spring-doc.cn

public class ExtendedStateVariableListener
		extends StateMachineListenerAdapter<String, String> {

	@Override
	public void extendedStateChanged(Object key, Object value) {
		// do something with changed variable
	}
}

Alternatively, you can implement a Spring Application context listener for OnExtendedStateChanged. As mentioned in Listening to State Machine Events, you can also listen all StateMachineEvent events. The following example uses onApplicationEvent to listen for state changes:spring-doc.cn

public class ExtendedStateVariableEventListener
		implements ApplicationListener<OnExtendedStateChanged> {

	@Override
	public void onApplicationEvent(OnExtendedStateChanged event) {
		// do something with changed variable
	}
}

Using StateContext

StateContext is one of the most important objects when working with a state machine, as it is passed into various methods and callbacks to give the current state of a state machine and where it is possibly going. You can think of it as a snapshot of the current state machine stage when is when StateContext is retreived.spring-doc.cn

In Spring Statemachine 1.0.x, StateContext usage was relatively naive in terms of how it was used to pass stuff around as a simple “POJO”. Starting from Spring Statemachine 1.1.x, its role has been greatly improved by making it a first class citizen in a state machine.

You can use StateContext to get access to the following:spring-doc.cn

StateContext is passed into various components, such as Action and Guard.spring-doc.cn

Stages

Stage is arepresentation of a stage on which a state machine is currently interacting with a user. The currently available stages are EVENT_NOT_ACCEPTED, EXTENDED_STATE_CHANGED, STATE_CHANGED, STATE_ENTRY, STATE_EXIT, STATEMACHINE_ERROR, STATEMACHINE_START, STATEMACHINE_STOP, TRANSITION, TRANSITION_START, and TRANSITION_END. These states may look familiar, as they match how you can interact with listeners (as described in Listening to State Machine Events).spring-doc.cn

Triggering Transitions

Driving a state machine is done by using transitions, which are triggered by triggers. The currently supported triggers are EventTrigger and TimerTrigger.spring-doc.cn

Using EventTrigger

EventTrigger is the most useful trigger, because it lets you directly interact with a state machine by sending events to it. These events are also called signals. You can add a trigger to a transition by associating a state with it during configuration. The following example shows how to do so:spring-doc.cn

@Autowired
StateMachine<String, String> stateMachine;

void signalMachine() {
	stateMachine
		.sendEvent(Mono.just(MessageBuilder
			.withPayload("E1").build()))
		.subscribe();

	Message<String> message = MessageBuilder
			.withPayload("E2")
			.setHeader("foo", "bar")
			.build();
	stateMachine.sendEvent(Mono.just(message)).subscribe();
}

Whether you send one event or multiple events, result is always a sequence of results. This is so because in a presence multiple reqions, results will come back from multiple machines in those regions. This is shown with method sendEventCollect which gives a list of results. Method itself is a just a syntactic sugar collecting Flux as list. If there is just one region, this list contains one result.spring-doc.cn

Message<String> message1 = MessageBuilder
	.withPayload("E1")
	.build();

Mono<List<StateMachineEventResult<String, String>>> results =
	stateMachine.sendEventCollect(Mono.just(message1));

results.subscribe();
Nothing happens until returned flux is subscribed. See more about it from StateMachineEventResult.

The preceding example sends an events by constructing a Mono wrapping a Message and subscribing into returned Flux of results. Message lets us add arbitrary extra information to an event, which is then visible to StateContext when (for example) you implement actions.spring-doc.cn

Message headers are generally passed on until machine runs to completion for a specific event. For example if an event is causing transition into a state A which have an anonymous transition into a state B, original event is available for actions or guards in state B.

It is also possible to send a Flux of messages instead of sending just one with a Mono.spring-doc.cn

Message<String> message1 = MessageBuilder
	.withPayload("E1")
	.build();
Message<String> message2 = MessageBuilder
	.withPayload("E2")
	.build();

Flux<StateMachineEventResult<String, String>> results =
	stateMachine.sendEvents(Flux.just(message1, message2));

results.subscribe();

StateMachineEventResult

StateMachineEventResult contains more detailed information about a result of a event sending. From this you can get a Region which handled an event, Message itself and what was an actual ResultType. From ResultType you can see if message was accepted, denied or deferred. Generally speaking when subscribtion completes, events are passed into a machine.spring-doc.cn

Using TimerTrigger

TimerTrigger is useful when something needs to be triggered automatically without any user interaction. Trigger is added to a transition by associating a timer with it during a configuration.spring-doc.cn

Currently, there are two types of supported timers, one that fires continuously and one that fires once a source state is entered. The following example shows how to use the triggers:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config2 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
			.withStates()
				.initial("S1")
				.state("S2")
				.state("S3");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("S1").target("S2").event("E1")
				.and()
			.withExternal()
				.source("S1").target("S3").event("E2")
				.and()
			.withInternal()
				.source("S2")
				.action(timerAction())
				.timer(1000)
				.and()
			.withInternal()
				.source("S3")
				.action(timerAction())
				.timerOnce(1000);
	}

	@Bean
	public TimerAction timerAction() {
		return new TimerAction();
	}
}

public class TimerAction implements Action<String, String> {

	@Override
	public void execute(StateContext<String, String> context) {
		// do something in every 1 sec
	}
}

The preceding example has three states: S1, S2, and S3. We have a normal external transition from S1 to S2 and from S1 to S3 with events E1 and E2, respectively. The interesting parts for working with TimerTrigger are when we define internal transitions for source states S2 and S3.spring-doc.cn

For both transitions, we invoke the Action bean (timerAction), where source state S2 uses timer and S3 uses timerOnce. Values given are in milliseconds (1000 milliseconds, or one second, in both cases).spring-doc.cn

Once a state machine receives event E1, it does a transition from S1 to S2 and the timer kicks in. When the state is S2, TimerTrigger runs and causes a transition associated with that state — in this case, the internal transition that has the timerAction defined.spring-doc.cn

Once a state machine receives the E2, event it does a transition from S1 to S3 and the timer kicks in. This timer is executed only once after the state is entered (after a delay defined in a timer).spring-doc.cn

Behind the scenes, timers are simple triggers that may cause a transition to happen. Defining a transition with a timer() keeps firing triggers and causes transition only if the source state is active. Transition with timerOnce() is a little different, as it triggers only after a delay when a source state is actually entered.
Use timerOnce() if you want something to happen after a delay exactly once when state is entered.

Listening to State Machine Events

There are use cases where you want to know what is happening with a state machine, react to something, or get logging details for debugging purposes. Spring Statemachine provides interfaces for adding listeners. These listeners then give an option to get callbacks when various state changes, actions, and so on happen.spring-doc.cn

You basically have two options: listen to Spring application context events or directly attach a listener to a state machine. Both of these basically provide the same information. One produces events as event classes, and the other produces callbacks via a listener interface. Both of these have pros and cons, which we discuss later.spring-doc.cn

Application Context Events

Application context events classes are OnTransitionStartEvent, OnTransitionEvent, OnTransitionEndEvent, OnStateExitEvent, OnStateEntryEvent, OnStateChangedEvent, OnStateMachineStart, OnStateMachineStop, and others that extend the base event class, StateMachineEvent. These can be used as is with a Spring ApplicationListener.spring-doc.cn

StateMachine sends context events through StateMachineEventPublisher. The default implementation is automatically created if a @Configuration class is annotated with @EnableStateMachine. The following example gets a StateMachineApplicationEventListener from a bean defined in a @Configuration class:spring-doc.cn

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 by using @EnableStateMachine, with StateMachine used to build a machine and registered as a bean, as the following example shows:spring-doc.cn

@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

By using StateMachineListener, you can either extend it and implement all callback methods or use the StateMachineListenerAdapter class, which contains stub method implementations and choose which ones to override. The following example uses the latter approach:spring-doc.cn

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 the preceding example, we created our own listener class (StateMachineEventListener) that extends StateMachineListenerAdapter.spring-doc.cn

The stateContext listener method gives access to various StateContext changes on a different stages. You can find more about about it in Using StateContext.spring-doc.cn

Once you have defined your own listener, you can registered it in a state machine by using the addStateListener method. It is a matter of flavor whether to hook it up within a spring configuration or do it manually at any time during the application life-cycle. The following example shows how to attach a listener:spring-doc.cn

public class Config7 {

	@Autowired
	StateMachine<States, Events> stateMachine;

	@Bean
	public StateMachineEventListener stateMachineEventListener() {
		StateMachineEventListener listener = new StateMachineEventListener();
		stateMachine.addStateListener(listener);
		return listener;
	}

}

Limitations and Problems

Spring application context is not the fastest event bus out there, so we advise giving some thought to the rate of events the state machine sends. For better performance, it may be better to use the StateMachineListener interface. For this specific reason, you can use the contextEvents flag with @EnableStateMachine and @EnableStateMachineFactory to disable Spring application context events, as shown in the preceding section. The following example shows how to disable Spring application context events:spring-doc.cn

@Configuration
@EnableStateMachine(contextEvents = false)
public class Config8
		extends EnumStateMachineConfigurerAdapter<States, Events> {
}

@Configuration
@EnableStateMachineFactory(contextEvents = false)
public class Config9
		extends EnumStateMachineConfigurerAdapter<States, Events> {
}

Context Integration

It is a little limited to do interaction with a state machine by either listening to its events or using actions with states and transitions. From time to time, this approach is going be too limited and verbose to create interaction with the application with which a state machine works. For this specific use case, we have made a Spring-style context integration that easily inserts state machine functionality into your beans.spring-doc.cn

The available annotations has been harmonized to enable access to the same state machine execution points that are available from Listening to State Machine Events.spring-doc.cn

You can use the @WithStateMachine annotation to associate a state machine with an existing bean. Then you can start adding supported annotations to the methods of that bean. The following example shows how to do so:spring-doc.cn

@WithStateMachine
public class Bean1 {

	@OnTransition
	public void anyTransition() {
	}
}

You can also attach any other state machine from an application context by using the annotation name field. The following example shows how to do so:spring-doc.cn

@WithStateMachine(name = "myMachineBeanName")
public class Bean2 {

	@OnTransition
	public void anyTransition() {
	}
}

Sometimes, it is more convenient to use machine id, which is something you can set to better identify multiple instances. This ID maps to the getId() method in the StateMachine interface. The following example shows how to use it:spring-doc.cn

@WithStateMachine(id = "myMachineId")
public class Bean16 {

	@OnTransition
	public void anyTransition() {
	}
}

When using StateMachineFactory to generate state machines the state machine using dynamic provided id, bean name will default to stateMachine it’s not possible to use @WithStateMachine (id = "some-id") since id is only known at runtime.spring-doc.cn

In such a cases, use either @WithStateMachine or @WithStateMachine(name = "stateMachine") and all state machines generated by the factory will be atached to your bean or beans.spring-doc.cn

You can also use @WithStateMachine as a meta-annotation, as shown in the preceding example. In this case, you could annotate your bean with WithMyBean. The following example shows how to do so:spring-doc.cn

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@WithStateMachine(name = "myMachineBeanName")
public @interface WithMyBean {
}
The return type of these methods does not matter and is effectively discarded.

Enabling Integration

You can enable all the features of @WithStateMachine by using the @EnableWithStateMachine annotation, which imports the needed configuration into the Spring Application Context. Both @EnableStateMachine and @EnableStateMachineFactory are already annotated with this annotation, so there is no need to add it again. However, if a machine is built and configured without configuration adapters, you must use @EnableWithStateMachine to use these features with @WithStateMachine. The following example shows how to do so:spring-doc.cn

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() {
	}
}
If a machine is not created as a bean, you need to set BeanFactory for a machine, as shown in the prededing example. Otherwise, tge machine is unaware of handlers that call your @WithStateMachine methods.

Method Parameters

Every annotation support exactly the same set of possible method parameters, but runtime behavior differs, depending on the annotation itself and the stage in which the annotated method is called. To better understand how context works, see Using StateContext.spring-doc.cn

For differences between method parameters, see the sections that desdribe the individual annotation, later in this document.

Effectively, all annotated methods are called by using Spring SPel expressions, which are built dynamically during the process. To make this work, these expressions needs to have a root object (against which they evaluate). This root object is a StateContext. We have also made some tweaks internally so that it is possible to access StateContext methods directly without going through the context handle.spring-doc.cn

The simplest method parameter is a StateContext itself. The following example shows how to use it:spring-doc.cn

@WithStateMachine
public class Bean3 {

	@OnTransition
	public void anyTransition(StateContext<String, String> stateContext) {
	}
}

You can access the rest of the StateContext content. The number and order of the parameters does not matter. The following example shows how to access the various parts of the StateContext content:spring-doc.cn

@WithStateMachine
public class Bean4 {

	@OnTransition
	public void anyTransition(
			@EventHeaders Map<String, Object> headers,
			@EventHeader("myheader1") Object myheader1,
			@EventHeader(name = "myheader2", required = false) String myheader2,
			ExtendedState extendedState,
			StateMachine<String, String> stateMachine,
			Message<String> message,
			Exception e) {
	}
}
Instead of getting all event headers with @EventHeaders, you can use @EventHeader, which can bound to a single header.

Transition Annotations

The annotations for transitions are @OnTransition, @OnTransitionStart, and @OnTransitionEnd.spring-doc.cn

These annotations behave exactly the same. To show how they work, we show how @OnTransition is used. Within this annotation, a property’s you can use source and target to qualify a transition. If source and target are left empty, any transition is matched. The following example shows how to use the @OnTransition annotation (remember that @OnTransitionStart and @OnTransitionEnd work the same way):spring-doc.cn

@WithStateMachine
public class Bean5 {

	@OnTransition(source = "S1", target = "S2")
	public void fromS1ToS2() {
	}

	@OnTransition
	public void anyTransition() {
	}
}

By default, you cannot use the @OnTransition annotation with a state and event enumerations that you have created, due to Java language limitations. For this reason, you need to use string representations.spring-doc.cn

Additionally, you can access Event Headers and ExtendedState by adding the needed arguments to a method. The method is then called automatically with these arguments. The following example shows how to do so:spring-doc.cn

@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, you can create a new annotation and use @OnTransition as a meta-annotation. This user-level annotation can make references to actual states and events enumerations, and the framework tries to match these in the same way. The following example shows how to do so:spring-doc.cn

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public @interface StatesOnTransition {

	States[] source() default {};

	States[] target() default {};
}

In the preceding example, we created a @StatesOnTransition annotation that defines source and target in a type-safe manner. The following example uses that annotation in a bean:spring-doc.cn

@WithStateMachine
public class Bean7 {

	@StatesOnTransition(source = States.S1, target = States.S2)
	public void fromS1ToS2() {
	}
}

State Annotations

The following annotations for states are available: @OnStateChanged, @OnStateEntry, and @OnStateExit. The following example shows how to use OnStateChanged annotation (the other two work the same way):spring-doc.cn

@WithStateMachine
public class Bean8 {

	@OnStateChanged
	public void anyStateChange() {
	}
}

As you can with Transition Annotations, you can define target and source states. The following example shows how to do so:spring-doc.cn

@WithStateMachine
public class Bean9 {

	@OnStateChanged(source = "S1", target = "S2")
	public void stateChangeFromS1toS2() {
	}
}

For type safety, new annotations need to be created for enumerations by using @OnStateChanged as a meta-annotation. The following examples show how to do so:spring-doc.cn

@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() {
	}
}

The methods for state entry and exit behave in the same way, as the following example shows:spring-doc.cn

@WithStateMachine
public class Bean11 {

	@OnStateEntry
	public void anyStateEntry() {
	}

	@OnStateExit
	public void anyStateExit() {
	}
}

Event Annotation

There is one event-related annotation. It is named @OnEventNotAccepted. If you specify the event property, you can listen for a specific event not being accepted. If you do not specify an event, you can list for any event not being accepted. The following example shows both ways to use the @OnEventNotAccepted annotation:spring-doc.cn

@WithStateMachine
public class Bean12 {

	@OnEventNotAccepted
	public void anyEventNotAccepted() {
	}

	@OnEventNotAccepted(event = "E1")
	public void e1EventNotAccepted() {
	}
}

State Machine Annotations

The following annotations are available for a state machine: @OnStateMachineStart, @OnStateMachineStop, and @OnStateMachineError.spring-doc.cn

During a state machine’s start and stop, lifecycle methods are called. The following example shows how to use @OnStateMachineStart and @OnStateMachineStop to listen to these events:spring-doc.cn

@WithStateMachine
public class Bean13 {

	@OnStateMachineStart
	public void onStateMachineStart() {
	}

	@OnStateMachineStop
	public void onStateMachineStop() {
	}
}

If a state machine goes into an error with exception, @OnStateMachineStop annotation is called. The following example shows how to use it:spring-doc.cn

@WithStateMachine
public class Bean14 {

	@OnStateMachineError
	public void onStateMachineError() {
	}
}

Extended State Annotation

There is one extended state-related annotation. It is named @OnExtendedStateChanged. You can also listen to changes only for specific key changes. The following example shows how to use the @OnExtendedStateChanged, both with and without a key property:spring-doc.cn

@WithStateMachine
public class Bean15 {

	@OnExtendedStateChanged
	public void anyStateChange() {
	}

	@OnExtendedStateChanged(key = "key1")
	public void key1Changed() {
	}
}

Using StateMachineAccessor

StateMachine is the main interface for communicating with a state machine. From time to time, you may need to get more dynamic and programmatic access to internal structures of a state machine and its nested machines and regions. For these use cases, StateMachine exposes a functional interface called StateMachineAccessor, which provides an interface to get access to individual StateMachine and Region instances.spring-doc.cn

StateMachineFunction is a simple functional interface that lets you apply the StateMachineAccess interface to a state machine. With JDK 7, these create code that is a little verbose code. However, with JDK 8 lambdas, the doce is relatively non-verbose.spring-doc.cn

The doWithAllRegions method gives access to all Region instances in a state machine. The following example shows how to use it:spring-doc.cn

stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.setRelay(stateMachine));

stateMachine.getStateMachineAccessor()
	.doWithAllRegions(access -> access.setRelay(stateMachine));

The doWithRegion method gives access to single Region instance in a state machine. The following example shows how to use it:spring-doc.cn

stateMachine.getStateMachineAccessor().doWithRegion(function -> function.setRelay(stateMachine));

stateMachine.getStateMachineAccessor()
	.doWithRegion(access -> access.setRelay(stateMachine));

The withAllRegions method gives access to all of the Region instances in a state machine. The following example shows how to use it:spring-doc.cn

for (StateMachineAccess<String, String> access : stateMachine.getStateMachineAccessor().withAllRegions()) {
	access.setRelay(stateMachine);
}

stateMachine.getStateMachineAccessor().withAllRegions()
	.stream().forEach(access -> access.setRelay(stateMachine));

The withRegion method gives access to single Region instance in a state machine. The following example shows how to use it:spring-doc.cn

stateMachine.getStateMachineAccessor()
	.withRegion().setRelay(stateMachine);

Using StateMachineInterceptor

Instead of using a StateMachineListener interface, you can use a StateMachineInterceptor. One conceptual difference is that you can use an interceptor to intercept and stop a current state change or change its transition logic. Instead of implementing a full interface, you can use an adapter class called StateMachineInterceptorAdapter to override the default no-op methods.spring-doc.cn

One recipe (Persist) and one sample ([statemachine-examples-persist]) are related to using an interceptor.

You can register an interceptor through StateMachineAccessor. The concept of an interceptor is a relatively deep internal feature and, thus, is not exposed directly through the StateMachine interface.spring-doc.cn

The following example shows how to add a StateMachineInterceptor and override selected methods:spring-doc.cn

stateMachine.getStateMachineAccessor()
	.withRegion().addStateMachineInterceptor(new StateMachineInterceptor<String, String>() {

		@Override
		public Message<String> preEvent(Message<String> message, StateMachine<String, String> stateMachine) {
			return message;
		}

		@Override
		public StateContext<String, String> preTransition(StateContext<String, String> stateContext) {
			return stateContext;
		}

		@Override
		public void preStateChange(State<String, String> state, Message<String> message,
				Transition<String, String> transition, StateMachine<String, String> stateMachine,
				StateMachine<String, String> rootStateMachine) {
		}

		@Override
		public StateContext<String, String> postTransition(StateContext<String, String> stateContext) {
			return stateContext;
		}

		@Override
		public void postStateChange(State<String, String> state, Message<String> message,
				Transition<String, String> transition, StateMachine<String, String> stateMachine,
				StateMachine<String, String> rootStateMachine) {
		}

		@Override
		public Exception stateMachineError(StateMachine<String, String> stateMachine,
				Exception exception) {
			return exception;
		}
	});
For more about the error handling shown in preceding example, see State Machine Error Handling.

State Machine Security

Security features are built atop of functionality from Spring Security. Security features are handy when it is required to protect part of a state machine execution and interaction with it.spring-doc.cn

We expect you to be fairly familiar with Spring Security, meaning that we do not go into details of how the overall security framework works. For this information, you should read the Spring Security reference documentation (available here).

The first level of defense with security is naturally protecting events, which really drive what is going to happen in a state machine. You can then define more fine-grained security settings for transitions and actions. This parallel to giving an employee access to a building and then giving access to specific rooms within the building and even the ability to turn on and off the lights in specific rooms. If you trust your users, event security may be all you need. If not, you need to apply more detailed security.spring-doc.cn

You can find more detailed information in Understanding Security.spring-doc.cn

For a complete example, see the Security sample.

Configuring Security

All generic configurations for security are done in SecurityConfigurer, which is obtained from StateMachineConfigurationConfigurer. By default, security is disabled, even if Spring Security classes are present. The following example shows how to enable security:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config4 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true)
				.transitionAccessDecisionManager(null)
				.eventAccessDecisionManager(null);
	}
}

If you absolutely need to, you can customize AccessDecisionManager for both events and transitions. If you do not define decision managers or set them to null, default managers are created internally.spring-doc.cn

Securing Events

Event security is defined on a global level by a SecurityConfigurer. The following example shows how to enable event security:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config1 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true)
				.event("true")
				.event("ROLE_ANONYMOUS", ComparisonType.ANY);
	}
}

In the preceding configuration example, we use an expression of true, which always evaluates to TRUE. Using an expression that always evaluates to TRUE would not make sense in a real application but shows the point that expression needs to return either TRUE or FALSE. We also defined an attribute of ROLE_ANONYMOUS and a ComparisonType of ANY. For more about using attributes and expressions, see Using Security Attributes and Expressions.spring-doc.cn

Securing Transitions

You can define transition security globally, as the following example shows.spring-doc.cn

@Configuration
@EnableStateMachine
static class Config6 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true)
				.transition("true")
				.transition("ROLE_ANONYMOUS", ComparisonType.ANY);
	}
}

If security is defined in a transition itself, it override any globally set security. The following example shows how to do so:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config2 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("S0")
				.target("S1")
				.event("A")
				.secured("ROLE_ANONYMOUS", ComparisonType.ANY)
				.secured("hasTarget('S1')");
	}
}

For more about using attributes and expressions, see Using Security Attributes and Expressions.spring-doc.cn

Securing Actions

There are no dedicated security definitions for actions in a state machine, but you can secure actions by using a global method security from Spring Security. This requires that an Action be defined as a proxied @Bean and its execute method be annotated with @Secured. The following example shows how to do so:spring-doc.cn

@Configuration
@EnableStateMachine
static class Config3 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withSecurity()
				.enabled(true);
	}

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		states
			.withStates()
				.initial("S0")
				.state("S1");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		transitions
			.withExternal()
				.source("S0")
				.target("S1")
				.action(securedAction())
				.event("A");
	}

	@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
	@Bean
	public Action<String, String> securedAction() {
		return new Action<String, String>() {

			@Secured("ROLE_ANONYMOUS")
			@Override
			public void execute(StateContext<String, String> context) {
			}
		};
	}

}

Global method security needs to be enabled with Spring Security. The following example shows how to do so:spring-doc.cn

@Configuration
public static class Config5 {

	@Bean
	public InMemoryUserDetailsManager userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
				.username("user")
				.password("password")
				.roles("USER")
				.build();
		return new InMemoryUserDetailsManager(user);
	}
}

See the Spring Security reference guide (available here) for more detail.spring-doc.cn

Using Security Attributes and Expressions

Generally, you can define security properties in either of two ways: by using security attributes and by using security expressions. Attributes are easier to use but are relatively limited in terms of functionality. Expressions provide more features but are a little bit harder to use.spring-doc.cn

Generic Attribute Usage

By default, AccessDecisionManager instances for events and transitions both use a RoleVoter, meaning you can use role attributes from Spring Security.spring-doc.cn

For attributes, we have three different comparison types: ANY, ALL, and MAJORITY. These comparison types map onto default access decision managers (AffirmativeBased, UnanimousBased, and ConsensusBased, respectively). If you have defined a custom AccessDecisionManager, the comparison type is effectively discarded, as it is used only to create a default manager.spring-doc.cn

Generic Expression Usage

Security expressions must return either TRUE or FALSE.spring-doc.cn

The base class for the expression root objects is SecurityExpressionRoot. It provides some common expressions, which are available in both transition and event security. The following table describes the most often used built-in expressions:spring-doc.cn

Table 1. Common built-in expressions
Expression Description

hasRole([role])spring-doc.cn

Returns true if the current principal has the specified role. By default, if the supplied role does not start with ROLE_, it is added. You can customize this by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.spring-doc.cn

hasAnyRole([role1,role2])spring-doc.cn

Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings). By default, if each supplied role does not start with ROLE_, it is added. You can customize this by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.spring-doc.cn

hasAuthority([authority])spring-doc.cn

Returns true if the current principal has the specified authority.spring-doc.cn

hasAnyAuthority([authority1,authority2])spring-doc.cn

Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings).spring-doc.cn

principalspring-doc.cn

Allows direct access to the principal object that represents the current user.spring-doc.cn

authenticationspring-doc.cn

Allows direct access to the current Authentication object obtained from the SecurityContext.spring-doc.cn

permitAllspring-doc.cn

Always evaluates to true.spring-doc.cn

denyAllspring-doc.cn

Always evaluates to false.spring-doc.cn

isAnonymous()spring-doc.cn

Returns true if the current principal is an anonymous user.spring-doc.cn

isRememberMe()spring-doc.cn

Returns true if the current principal is a remember-me user.spring-doc.cn

isAuthenticated()spring-doc.cn

Returns true if the user is not anonymous.spring-doc.cn

isFullyAuthenticated()spring-doc.cn

Returns true if the user is not an anonymous or a remember-me user.spring-doc.cn

hasPermission(Object target, Object permission)spring-doc.cn

Returns true if the user has access to the provided target for the given permission — for example, hasPermission(domainObject, 'read').spring-doc.cn

hasPermission(Object targetId, String targetType, Object permission)spring-doc.cn

Returns true if the user has access to the provided target for the given permission — for example, hasPermission(1, 'com.example.domain.Message', 'read').spring-doc.cn

Event Attributes

You can match an event ID by using a prefix of EVENT_. For example, matching event A would match an attribute of EVENT_A.spring-doc.cn

Event Expressions

The base class for the expression root object for events is EventSecurityExpressionRoot. It provides access to a Message object, which is passed around with eventing. EventSecurityExpressionRoot has only one method, which the following table describes:spring-doc.cn

Table 2. Event expressions
Expression Description

hasEvent(Object event)spring-doc.cn

Returns true if the event matches given event.spring-doc.cn

Transition Attributes

When matching transition sources and targets, you can use the TRANSITION_SOURCE_ and TRANSITION_TARGET_ prefixes respectively.spring-doc.cn

Transition Expressions

The base class for the expression root object for transitions is TransitionSecurityExpressionRoot. It provides access to a Transition object, which is passed around for transition changes. TransitionSecurityExpressionRoot has two methods, which the following table describes:spring-doc.cn

Table 3. Transition expressions
Expression Description

hasSource(Object source)spring-doc.cn

Returns true if the transition source matches given source.spring-doc.cn

hasTarget(Object target)spring-doc.cn

Returns true if the transition target matches given target.spring-doc.cn

Understanding Security

This section provides more detailed information about how security works within a state machine. You may not really need to know, but it is always better to be transparent instead of hiding all the magic what happens behind the scenes.spring-doc.cn

Security makes sense only if Spring Statemachine runs in a walled garden where user have no direct access to the application and could consequently modify Spring Security’s SecurityContext hold in a local thread. If the user controls the JVM, then effectively there is no security at all.

The integration point for security is created with a StateMachineInterceptor, which is then automatically added into a state machine if security is enabled. The specific class is StateMachineSecurityInterceptor, which intercepts events and transitions. This interceptor then consults Spring Security’s AccessDecisionManager to determine whether an event can be sent or whether a transition can be executed. Effectively, if a decision or a vote with a AccessDecisionManager results in an exception, the event or transition is denied.spring-doc.cn

Due to how AccessDecisionManager from Spring Security works, we need one instance of it per secured object. This is one reason why there are different managers for events and transitions. In this case, events and transitions are different class objects that we secure.spring-doc.cn

By default, for events, voters (EventExpressionVoter, EventVoter, and RoleVoter) are added into an AccessDecisionManager.spring-doc.cn

By default, for transitions, voters (TransitionExpressionVoter, TransitionVoter, and RoleVoter) are added into an AccessDecisionManager.spring-doc.cn

State Machine Error Handling

If a state machine detects an internal error during a state transition logic, it may throw an exception. Before this exception is processed internally, you are given a chance to intercept.spring-doc.cn

Normally, you can use StateMachineInterceptor to intercept errors and the following listing shows an example of it:spring-doc.cn

StateMachine<String, String> stateMachine;

void addInterceptor() {
	stateMachine.getStateMachineAccessor()
			.doWithRegion(function ->
				function.addStateMachineInterceptor(new StateMachineInterceptorAdapter<String, String>() {
					@Override
					public Exception stateMachineError(StateMachine<String, String> stateMachine,
													   Exception exception) {
						return exception;
					}
				})
			);

}

When errors are detected, the normal event notify mechanism is executed. This lets you use either a StateMachineListener or a Spring Application context event listener. For more about these, see Listening to State Machine Events.spring-doc.cn

Having said that, the following example shows a simple listener:spring-doc.cn

public class ErrorStateMachineListener
		extends StateMachineListenerAdapter<String, String> {

	@Override
	public void stateMachineError(StateMachine<String, String> stateMachine, Exception exception) {
		// do something with error
	}
}

The following example shows a generic ApplicationListener checking StateMachineEvent:spring-doc.cn

public class GenericApplicationEventListener
		implements ApplicationListener<StateMachineEvent> {

	@Override
	public void onApplicationEvent(StateMachineEvent event) {
		if (event instanceof OnStateMachineError) {
			// do something with error
		}
	}
}

You can also directly define ApplicationListener to recognize only StateMachineEvent instances, as the following example shows:spring-doc.cn

public class ErrorApplicationEventListener
		implements ApplicationListener<OnStateMachineError> {

	@Override
	public void onApplicationEvent(OnStateMachineError event) {
		// do something with error
	}
}
Actions defined for transitions also have their own error handling logic. See Transition Action Error Handling.

With a reactive api’s it is possible to get Action execution error back from a StateMachineEventResult. Having simple machine which errors within action transitioning into state S1.spring-doc.cn

@Configuration
@EnableStateMachine
static class Config1 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
		states
			.withStates()
				.initial("SI")
				.stateEntry("S1", (context) -> {
					throw new RuntimeException("example error");
				});
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
		transitions
			.withExternal()
				.source("SI")
				.target("S1")
				.event("E1");
	}
}

Below test concept shows how possible error can be consumed from a StateMachineEventResult.spring-doc.cn

@Autowired
private StateMachine<String, String> machine;

@Test
public void testActionEntryErrorWithEvent() throws Exception {
	StepVerifier.create(machine.startReactively()).verifyComplete();
	assertThat(machine.getState().getIds()).containsExactlyInAnyOrder("SI");

	StepVerifier.create(machine.sendEvent(Mono.just(MessageBuilder.withPayload("E1").build())))
		.consumeNextWith(result -> {
			StepVerifier.create(result.complete()).consumeErrorWith(e -> {
				assertThat(e).isInstanceOf(StateMachineException.class).cause().hasMessageContaining("example error");
			}).verify();
		})
		.verifyComplete();

	assertThat(machine.getState().getIds()).containsExactlyInAnyOrder("S1");
}
Error in entry/exit actions will not prevent transition to happen.

State Machine Services

StateMachine services are higher-level implementations meant to provide more user-level functionalities to ease normal runtime operations. Currently, only one service interface (StateMachineService) exists.spring-doc.cn

Using StateMachineService

StateMachineService is an interface that is meant to handle running machines and have simple methods to “acquire” and “release” machines. It has one default implementation, named DefaultStateMachineService.spring-doc.cn

Persisting a State Machine

Traditionally, an instance of a state machine is used as is within a running program. You can achieve more dynamic behavior by using dynamic builders and factories, which allows state machine instantiation on-demand. Building an instance of a state machine is a relatively heavy operation. Consequently, if you need to (for example) handle an arbitrary state change in a database by using a state machine, you need to find a better and faster way to do it.spring-doc.cn

The persist feature lets you save a state of a state machine into an external repository and later reset a state machine based off the serialized state. For example, if you have a database table keeping orders, it would be way too expensive to update an order state with a state machine if a new instance would need to be built for every change. The persist feature lets you reset a state machine state without instantiating a new state machine instance.spring-doc.cn

There is one recipe (see Persist) and one sample (see [statemachine-examples-persist]) that provide more info about persisting states.

While you can build a custom persistence feature by using a StateMachineListener, it has one conceptual problem. When a listener notifies about a change of state, the state change has already happened. If a custom persistent method within a listener fails to update the serialized state in an external repository, the state in a state machine and the state in an external repository are then in an inconsistent state.spring-doc.cn

You can instead use a state machine interceptor to try to save the serialized state into external storage during the state change within a state machine. If this interceptor callback fails, you can halt the state change attempt and, instead of ending in an inconsistent state, you can then handle this error manually. See Using StateMachineInterceptor for how to use interceptors.spring-doc.cn

Using StateMachineContext

You cannot persist a StateMachine by using normal java serialization, as the object graph is too rich and contains too many dependencies on other Spring context classes. StateMachineContext is a runtime representation of a state machine that you can use to restore an existing machine into a state represented by a particular StateMachineContext object.spring-doc.cn

StateMachineContext contains two different ways to include information for a child context. These are generally used when a machine contains orthogonal regions. First, a context can have a list of child contexts that can be used as is if they exist. Second, you can include a list of references that are used if raw context children are not in place. These child references are really the only way to persist a machine where multiple parallel regions are running independently.spring-doc.cn

The Data Multi Persist sample shows how you can persist parallel regions.

Using StateMachinePersister

Building a StateMachineContext and then restoring a state machine from it has always been a little bit of “black magic” if done manually. The StateMachinePersister interface aims to ease these operations by providing persist and restore methods. The default implementation of this interface is DefaultStateMachinePersister.spring-doc.cn

We can show how to use a StateMachinePersister by following a snippets from tests. We start by creating two similar configurations (machine1 and machine2) for a state machine. Note that we could build different machines for this demonstration in other ways but this way works for this case. The following example configures the two state machines:spring-doc.cn

@Configuration
@EnableStateMachine(name = "machine1")
static class Config1 extends Config {
}

@Configuration
@EnableStateMachine(name = "machine2")
static class Config2 extends Config {
}

static class Config extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
		states
			.withStates()
				.initial("S1")
				.state("S1")
				.state("S2");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
		transitions
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1");
	}
}

As we are using a StateMachinePersist object, we can create an in-memory implementation.spring-doc.cn

This in-memory sample is only for demonstration purposes. For real applications, you should use a real persistent storage implementation.

The following listing shows how to use the in-memory sample:spring-doc.cn

static class InMemoryStateMachinePersist implements StateMachinePersist<String, String, String> {

	private final HashMap<String, StateMachineContext<String, String>> contexts = new HashMap<>();

	@Override
	public void write(StateMachineContext<String, String> context, String contextObj) throws Exception {
		contexts.put(contextObj, context);
	}

	@Override
	public StateMachineContext<String, String> read(String contextObj) throws Exception {
		return contexts.get(contextObj);
	}
}

After we have instantiated the two different machines, we can transfer machine1 into state S2 through event E1. Then we can persist it and restore machine2. The following example shows how to do so:spring-doc.cn

InMemoryStateMachinePersist stateMachinePersist = new InMemoryStateMachinePersist();
StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
stateMachine1.startReactively().block();

stateMachine1
	.sendEvent(Mono.just(MessageBuilder
		.withPayload("E1").build()))
	.blockLast();
assertThat(stateMachine1.getState().getIds()).containsExactly("S2");

persister.persist(stateMachine1, "myid");
persister.restore(stateMachine2, "myid");
assertThat(stateMachine2.getState().getIds()).containsExactly("S2");

Using Redis

RepositoryStateMachinePersist (which implements StateMachinePersist) offers support for persisting a state machine into Redis. The specific implementation is a RedisStateMachineContextRepository, which uses kryo serialization to persist a StateMachineContext into Redis.spring-doc.cn

For StateMachinePersister, we have a Redis-related RedisStateMachinePersister implementation, which takes an instance of a StateMachinePersist and uses String as its context object.spring-doc.cn

See the Event Service sample for detailed usage.

RedisStateMachineContextRepository needs a RedisConnectionFactory for it to work. We recommend using a JedisConnectionFactory for it, as the preceding example shows.spring-doc.cn

Using StateMachineRuntimePersister

StateMachineRuntimePersister is a simple extension to StateMachinePersist that adds an interface-level method to get StateMachineInterceptor associated with it. This interceptor is then required to persist a machine during state changes without needing to stop and start a machine.spring-doc.cn

Currently, there are implementations for this interface for the supported Spring Data Repositories. These implementations are JpaPersistingStateMachineInterceptor, MongoDbPersistingStateMachineInterceptor, and RedisPersistingStateMachineInterceptor.spring-doc.cn

See the Data Persist sample for detailed usage.

Spring Boot Support

The auto-configuration module (spring-statemachine-autoconfigure) contains all the logic for integrating with Spring Boot, which provides functionality for auto-configuration and actuators. All you need is to have this Spring Statemachine library as part of a boot application.spring-doc.cn

Monitoring and Tracing

BootStateMachineMonitor is created automatically and associated with a state machine. BootStateMachineMonitor is a custom StateMachineMonitor implementation that integrates with Spring Boot’s MeterRegistry and endpoints through a custom StateMachineTraceRepository. Optionally, you can disable this auto-configuration by setting the spring.statemachine.monitor.enabled key to false. The Monitoring sample shows how to use this auto-configuration.spring-doc.cn

Repository Config

If the required classes are found from the classpath, Spring Data Repositories and entity class scanning is automatically auto-configured for Repository Support.spring-doc.cn

The currently supported configurations are JPA, Redis, and MongoDB. You can disable repository auto-configuration by using the spring.statemachine.data.jpa.repositories.enabled, spring.statemachine.data.redis.repositories.enabled and spring.statemachine.data.mongo.repositories.enabled properties, respectively.spring-doc.cn

Monitoring a State Machine

You can use StateMachineMonitor to get more information about the durations of how long transitions and actions take to execute. The following listing shows how this interface is implemented.spring-doc.cn

public class TestStateMachineMonitor extends AbstractStateMachineMonitor<String, String> {

	@Override
	public void transition(StateMachine<String, String> stateMachine, Transition<String, String> transition,
			long duration) {
	}

	@Override
	public void action(StateMachine<String, String> stateMachine,
			Function<StateContext<String, String>, Mono<Void>> action, long duration) {
	}
}

Once you have a StateMachineMonitor implementation, you can add it to a state machine through configuration, as the following example shows:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config1 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withMonitoring()
				.monitor(stateMachineMonitor());
	}

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
		states
			.withStates()
				.initial("S1")
				.state("S2");
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
		transitions
			.withExternal()
				.source("S1")
				.target("S2")
				.event("E1");
	}

	@Bean
	public StateMachineMonitor<String, String> stateMachineMonitor() {
		return new TestStateMachineMonitor();
	}
}
See the Monitoring sample for detailed usage.

Using Distributed States

Distributed state is probably one of a most complicated concepts of a Spring state machine. What exactly is a distributed state? A state within a single state machine is naturally really simple to understand, but, when there is a need to introduce a shared distributed state through a state machine, things get a little complicated.spring-doc.cn

Distributed state functionality is still a preview feature and is not yet considered to be stable in this particular release. We expect this feature to mature towards its first official release.

For information about generic configuration support, see Configuring Common Settings. For an actual usage example, see the Zookeeper sample.spring-doc.cn

A distributed state machine is implemented through a DistributedStateMachine class that wraps an actual instance of a StateMachine. DistributedStateMachine intercepts communication with a StateMachine instance and works with distributed state abstractions handled through the StateMachineEnsemble interface. Depending on the actual implementation, you can also use the StateMachinePersist interface to serialize a StateMachineContext, which contains enough information to reset a StateMachine.spring-doc.cn

While a distributed state machine is implemented through an abstraction, only one implementation currently exists. It is based on Zookeeper.spring-doc.cn

The following example shows how to configure a Zookeeper-based distributed state machine`:spring-doc.cn

@Configuration
@EnableStateMachine
public class Config
		extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineConfigurationConfigurer<String, String> config)
			throws Exception {
		config
			.withDistributed()
				.ensemble(stateMachineEnsemble())
				.and()
			.withConfiguration()
				.autoStartup(true);
	}

	@Override
	public void configure(StateMachineStateConfigurer<String, String> states)
			throws Exception {
		// config states
	}

	@Override
	public void configure(StateMachineTransitionConfigurer<String, String> transitions)
			throws Exception {
		// config transitions
	}

	@Bean
	public StateMachineEnsemble<String, String> stateMachineEnsemble()
			throws Exception {
		return new ZookeeperStateMachineEnsemble<String, String>(curatorClient(), "/zkpath");
	}

	@Bean
	public CuratorFramework curatorClient()
			throws Exception {
		CuratorFramework client = CuratorFrameworkFactory
				.builder()
				.defaultData(new byte[0])
				.connectString("localhost:2181").build();
		client.start();
		return client;
	}

}

You can find the current technical documentation for a Zookeeker-based distributed state machine in the appendix.spring-doc.cn

Using ZookeeperStateMachineEnsemble

ZookeeperStateMachineEnsemble itself needs two mandatory settings, an instance of curatorClient and a basePath. The client is a CuratorFramework, and the path is the root of a tree in a Zookeeper instance.spring-doc.cn

Optionally, you can set cleanState, which defaults to TRUE and clears existing data if no members exists in an ensemble. You can set it to FALSE if you want to preserve distributed state within application restarts.spring-doc.cn

Optionally, you can set the size of a logSize (defaults to 32) to a keep history of state changes. The value of this setting must be a power of two. 32 is generally a good default value. If a particular state machine is left behind by more than the size of the log, it is put into an error state and disconnected from the ensemble, indicating it has lost its history and its ability to fully reconstruct the synchronized status.spring-doc.cn

Testing Support

We have also added a set of utility classes to ease testing of state machine instances. These are used in the framework itself but are also very useful for end users.spring-doc.cn

StateMachineTestPlanBuilder builds a StateMachineTestPlan, which has one method (called test()). That method runs a plan. StateMachineTestPlanBuilder contains a fluent builder API to let you add steps to a plan. During these steps, you can send events and check various conditions, such as state changes, transitions, and extended state variables.spring-doc.cn

The following example uses StateMachineBuilder to build a state machine:spring-doc.cn

private StateMachine<String, String> buildMachine() throws Exception {
	StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();

	builder.configureConfiguration()
		.withConfiguration()
			.autoStartup(true);

	builder.configureStates()
			.withStates()
				.initial("SI")
				.state("S1");

	builder.configureTransitions()
			.withExternal()
				.source("SI").target("S1")
				.event("E1")
				.action(c -> {
					c.getExtendedState().getVariables().put("key1", "value1");
				});

	return builder.build();
}

In the following test plan, we have two steps. First, we check that the initial state (SI) is indeed set. Second, we send an event (E1) and expect one state change to happen and expect the machine to end up in a state of S1. The following listing shows the test plan:spring-doc.cn

StateMachine<String, String> machine = buildMachine();
StateMachineTestPlan<String, String> plan =
		StateMachineTestPlanBuilder.<String, String>builder()
			.defaultAwaitTime(2)
			.stateMachine(machine)
			.step()
				.expectStates("SI")
				.and()
			.step()
				.sendEvent("E1")
				.expectStateChanged(1)
				.expectStates("S1")
				.expectVariable("key1")
				.expectVariable("key1", "value1")
				.expectVariableWith(hasKey("key1"))
				.expectVariableWith(hasValue("value1"))
				.expectVariableWith(hasEntry("key1", "value1"))
				.expectVariableWith(not(hasKey("key2")))
				.and()
			.build();
plan.test();

These utilities are also used within a framework to test distributed state machine features. Note that you can add multiple machines to a plan. If you add multiple machines, you can also choose to send an event a particular machine, a random machine, or all machines.spring-doc.cn

The preceding testing example uses the following Hamcrest imports:spring-doc.cn

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.hamcrest.collection.IsMapContaining.hasValue;

import org.junit.jupiter.api.Test;

import static org.hamcrest.collection.IsMapContaining.hasEntry;
All possible options for expected results are documented in the Javadoc for StateMachineTestPlanStepBuilder.

Eclipse Modeling Support

Defining a state machine configuration with UI modeling is supported through the Eclipse Papyrus framework.spring-doc.cn

From the Eclipse wizard, you can create a new Papyrus Model with the UML Diagram Language. In this example, it is named simple-machine. Then you have an option to choose from various diagram kinds, and you must choose a StateMachine Diagram.spring-doc.cn

We want to create a machine that has two states (S1 and S2), where S1 is the initial state. Then, we need to create event E1 to do a transition from S1 to S2. In Papyrus, a machine would then look like something the following example:spring-doc.cn

simple machine

Behind the scenes, a raw UML file would look like the following example:spring-doc.cn

<?xml version="1.0" encoding="UTF-8"?>
<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_AMP3IP8fEeW45bORGB4c_A" name="RootElement">
  <packagedElement xmi:type="uml:StateMachine" xmi:id="_AMRFQP8fEeW45bORGB4c_A" name="StateMachine">
    <region xmi:type="uml:Region" xmi:id="_AMRsUP8fEeW45bORGB4c_A" name="Region1">
      <transition xmi:type="uml:Transition" xmi:id="_chgcgP8fEeW45bORGB4c_A" source="_EZrg4P8fEeW45bORGB4c_A" target="_FAvg4P8fEeW45bORGB4c_A">
        <trigger xmi:type="uml:Trigger" xmi:id="_hs5jUP8fEeW45bORGB4c_A" event="_NeH84P8fEeW45bORGB4c_A"/>
      </transition>
      <transition xmi:type="uml:Transition" xmi:id="_egLIoP8fEeW45bORGB4c_A" source="_Fg0IEP8fEeW45bORGB4c_A" target="_EZrg4P8fEeW45bORGB4c_A"/>
      <subvertex xmi:type="uml:State" xmi:id="_EZrg4P8fEeW45bORGB4c_A" name="S1"/>
      <subvertex xmi:type="uml:State" xmi:id="_FAvg4P8fEeW45bORGB4c_A" name="S2"/>
      <subvertex xmi:type="uml:Pseudostate" xmi:id="_Fg0IEP8fEeW45bORGB4c_A"/>
    </region>
  </packagedElement>
  <packagedElement xmi:type="uml:Signal" xmi:id="_L01D0P8fEeW45bORGB4c_A" name="E1"/>
  <packagedElement xmi:type="uml:SignalEvent" xmi:id="_NeH84P8fEeW45bORGB4c_A" name="SignalEventE1" signal="_L01D0P8fEeW45bORGB4c_A"/>
</uml:Model>
When opening an existing model that has been defined as UML, you have three files: .di, .notation, and .uml. If a model was not created in your eclipse’s session, it does not understand how to open an actual state chart. This is a known issue in the Papyrus plugin, and there is an easy workaround. In a Papyrus perspective, you can see a model explorer for your model. Double click Diagram StateMachine Diagram, which instructs Eclipse to open this specific model in its proper Papyrus modeling plugin.

Using UmlStateMachineModelFactory

After a UML file is in place in your project, you can import it into your configuration by using StateMachineModelConfigurer, where StateMachineModelFactory is associated with a model. UmlStateMachineModelFactory is a special factory that knows how to process a Eclipse Papyrus_generated UML structure. The source UML file can either be given as a Spring Resource or as a normal location string. The following example shows how to create an instance of UmlStateMachineModelFactory:spring-doc.cn

@Configuration
@EnableStateMachine
public static class Config1 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
		model
			.withModel()
				.factory(modelFactory());
	}

	@Bean
	public StateMachineModelFactory<String, String> modelFactory() {
		return new UmlStateMachineModelFactory("classpath:org/springframework/statemachine/uml/docs/simple-machine.uml");
	}
}

As usual, Spring Statemachine works with guards and actions, which are defined as beans. Those need to be hooked into UML by its internal modeling structure. The following sections show how customized bean references are defined within UML definitions. Note that it is also possible to register particular methods manually without defining those as beans.spring-doc.cn

If UmlStateMachineModelFactory is created as a bean, its ResourceLoader is automatically wired to find registered actions and guards. You can also manually define a StateMachineComponentResolver, which is then used to find these components. The factory also has registerAction and registerGuard methods, which you can use to register these components. For more about this, see Using StateMachineComponentResolver.spring-doc.cn

A UML model is relatively loose when it comes to an implementation such as Spring Statemachine itself. Spring Statemachine leaves how to implement a lot of features and functionalities up to the actual implementation. The following sections go through how Spring Statemachine implements UML models based on the Eclipse Papyrus plugin.spring-doc.cn

Using StateMachineComponentResolver

The next example shows how UmlStateMachineModelFactory is defined with a StateMachineComponentResolver, which registers the myAction and myGuard functions, respectively. Note that these components are not created as beans. The following listing shows the example:spring-doc.cn

@Configuration
@EnableStateMachine
public static class Config2 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
		model
			.withModel()
				.factory(modelFactory());
	}

	@Bean
	public StateMachineModelFactory<String, String> modelFactory() {
		UmlStateMachineModelFactory factory = new UmlStateMachineModelFactory(
				"classpath:org/springframework/statemachine/uml/docs/simple-machine.uml");
		factory.setStateMachineComponentResolver(stateMachineComponentResolver());
		return factory;
	}

	@Bean
	public StateMachineComponentResolver<String, String> stateMachineComponentResolver() {
		DefaultStateMachineComponentResolver<String, String> resolver = new DefaultStateMachineComponentResolver<>();
		resolver.registerAction("myAction", myAction());
		resolver.registerGuard("myGuard", myGuard());
		return resolver;
	}

	public Action<String, String> myAction() {
		return new Action<String, String>() {

			@Override
			public void execute(StateContext<String, String> context) {
			}
		};
	}

	public Guard<String, String> myGuard() {
		return new Guard<String, String>() {

			@Override
			public boolean evaluate(StateContext<String, String> context) {
				return false;
			}
		};
	}
}

Creating a Model

We start by creating an empty state machine model, shown in the following image:spring-doc.cn

papyrus gs 1

You can start by creating a new model and giving it a name, as the following image shows:spring-doc.cn

papyrus gs 2

Then you need to choose StateMachine Diagram, as follows:spring-doc.cn

papyrus gs 3

You end up with an empty state machine.spring-doc.cn

In the preceding images, you should have created a sample named model. You should have wound up with three files: model.di, model.notation, and model.uml. You can then used these files in any other Eclipse instance. Further, you can import model.uml into a Spring Statemachine.spring-doc.cn

Defining States

The state identifier comes from a component name in a diagram. You must have an initial state in your machine, which you can do by adding a root element and then drawing a transition to your own initial state, as the following image shows:spring-doc.cn

papyrus gs 4

In the preceding image, we added a root element and an initial state (S1). Then we drew a transition between those two to indicate that S1 is an initial state.spring-doc.cn

papyrus gs 5

In the preceding image, we added a second state (S2) and added a transition between S1 and S2 (indicating that we have two states).spring-doc.cn

Defining Events

To associate an event with a transition, you need to create a Signal (E1, in this case). To do so, choose RootElement → New Child → Signal. The following image shows the result:spring-doc.cn

papyrus gs 6

Then you need to crate a SignalEvent with the new Signal, E1. To do so, choose RootElement → New Child → SignalEvent. The following image shows the result:spring-doc.cn

papyrus gs 7

Now that you have defined a SignalEvent, you can use it to associate a trigger with a transition. For more about that, see Defining Transitions.spring-doc.cn

Deferring an Event

You can defer events to process them at a more appropriate time. In UML, this is done from a state itself. Choose any state, create a new trigger under Deferrable trigger and choose the SignalEvent which matches the Signal you want to defer.spring-doc.cn

Defining Transitions

You can create a transition by drawing a transition line between the source and target states. In the preceding images, we have states S1 and S2 and an anonymous transition between the two. We want to associate event E1 with that transition. We choose a transition, create a new trigger, and define SignalEventE1 for that, as the following image shows:spring-doc.cn

papyrus gs 8

This gives you something like the arrangement shown in the following image:spring-doc.cn

papyrus gs 9
If you omit SignalEvent for a transition, it becomes an anonymous transition.

Defining Timers

Transitions can also happen based on timed events. Spring Statemachine support two types of timers, ones which fires continuously on a background and ones which fires once with a delay when state is entered.spring-doc.cn

To add a new TimeEvent child to Model Explorer, modify When as an expression defined as LiteralInteger. The value of it (in milliseconds) becomes the timer. Leave Is Relative false to make the timer fire continuously.spring-doc.cn

papyrus gs 10

To define one timed based event that triggers when a state is entered, the process is exactly same as described earlier, but leave Is Relative set to true. The following image shows the result:spring-doc.cn

papyrus gs 11

Then the user can pick one of these timed events instead of a signal event for a particular transition.spring-doc.cn

Defining a Choice

A choice is defined by drawing one incoming transition into a CHOICE state and drawing multiple outgoing transitions from it to target states. The configuration model in our StateConfigurer lets you define an if/elseif/else structure. However, with UML, we need to work with individual Guards for outgoing transitions.spring-doc.cn

You must ensure that the guards defined for transitions do not overlap so that, whatever happens, only one guard evaluates to TRUE at any given time. This gives precise and predictable results for choice branch evaluation. Also we recommend leaving one transition without a guard so that at least one transition path is guaranteed. The following image shows the result of making a choice with three branches:spring-doc.cn

papyrus gs 16
Junction works similarly same, except that it allows multiple incoming transitions. Thus, its behavior compared to Choice is purely academic. The actual logic to select the outgoing transition is exactly the same.

Defining a Junction

Defining Entry and Exit Points

You can use EntryPoint and ExitPoint to create controlled entry and exit with states that have sub-states. In the following state chart, events E1 and E2 have normal state behavior by entering and exiting state S2, where normal state behavior happens by entering initial state S21.spring-doc.cn

Using event E3 takes the machine into the ENTRY EntryPoint, which then leads to S22 without activating initial state S21 at any time. Similarly the EXIT ExitPoint with event E4 controls the specific exit into state S4, while normal exit behavior from S2 would take the machine into state S3. While on state S22, you can choose from events E4 and E2 to take the machine into states S3 or S4, respectively. The following image shows the result:spring-doc.cn

papyrus gs 17
If state is defined as a sub-machine reference and you need to use entry and exit points, you must externally define a ConnectionPointReference, with its entry and exit reference set to point to a correct entry or exit point within a submachine reference. Only after that, is it possible to target a transition that correctly links from the outside to the inside of a sub-machine reference. With ConnectionPointReference, you may need to find these settings from Properties → Advanced → UML → Entry/Exit. The UML specification lets you define multiple entries and exits. However, with a state machine, only one is allowed.

Defining History States

When working with history states, three different concepts are in play. UML defines a Deep History and a Shallow History. The Default History State comes into play when history state is not yet known. These are represented in following sections.spring-doc.cn

Shallow History

In the following image, Shallow History is selected and a transition is defined into it:spring-doc.cn

papyrus gs 18

Deep History

Deep History is used for state that has other deep nested states, thus giving a chance to save whole nested state structure. The following image shows a definition that uses Deep History:spring-doc.cn

papyrus gs 19

Default History

In cases where a Transition terminates on a history when the state has not been entered before it had reached its final state, there is an option to force a transition to a specific substate, using the default history mechanism. For this to happen, you must define a transition into this default state. This is the transition from SH to S22.spring-doc.cn

In the following image, state S22 is entered if state S2 has never been active, as its history has never been recorded. If state S2 has been active, then either S20 or S21 gets chosen.spring-doc.cn

papyrus gs 20

Defining Forks and Joins

Both Fork and Join are represented as bars in Papyrus. As shown in the next image, you need to draw one outgoing transition from FORK into state S2 to have orthogonal regions. JOIN is then the reverse, where joined states are collected together from incoming transitions.spring-doc.cn

papyrus gs 21

Defining Actions

You can assoiate swtate entry and exit actions by using a behavior. For more about this, see Defining a Bean Reference.spring-doc.cn

Using an Initial Action

An initial action (as shown in Configuring Actions) is defined in UML by adding an action in the transition that leads from the Initial State marker into the actual state. This Action is then run when the state machine is started.spring-doc.cn

Defining Guards

You can define a guard by first adding a Constraint and then defining its Specification as OpaqueExpression, which works in the same way as Defining a Bean Reference.spring-doc.cn

Defining a Bean Reference

When you need to make a bean reference in any UML effect, action, or guard, you can do so with FunctionBehavior or OpaqueBehavior, where the defined language needs to be bean and the language body msut have a bean reference id.spring-doc.cn

Defining a SpEL Reference

When you need to use a SpEL expression instead of a bean reference in any UML effect, action, or guard, you can do so by using FunctionBehavior or OpaqueBehavior, where the defined language needs to be spel and the language body must be a SpEL expression.spring-doc.cn

Using a Sub-Machine Reference

Normally, when you use sub-states, you draw those into the state chart itself. The chart may become too complex and big to follow, so we also support defining a sub-state as a state machine reference.spring-doc.cn

To create a sub-machine reference, you must first create a new diagram and give it a name (for example, SubStateMachine Diagram). The following image shows the menu choices to use:spring-doc.cn

papyrus gs 12

Give the new diagram the design you need. The following image shows a simple design as an example:spring-doc.cn

papyrus gs 13

From the state you want to link (in this case,m state S2), click the Submachine field and choose your linked machine (in our example, SubStateMachine).spring-doc.cn

papyrus gs 14

Finally, in the following image, you can see that state S2 is linked to SubStateMachine as a sub-state.spring-doc.cn

papyrus gs 15

Using a Machine Import

It’s also possible to use import functionality where uml files can reference to other models.spring-doc.cn

papyrus gs 22

Within UmlStateMachineModelFactory it’s possible to use additional resources or locations to define referenced model files.spring-doc.cn

@Configuration
@EnableStateMachine
public static class Config3 extends StateMachineConfigurerAdapter<String, String> {

	@Override
	public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
		model
			.withModel()
				.factory(modelFactory());
	}

	@Bean
	public StateMachineModelFactory<String, String> modelFactory() {
		return new UmlStateMachineModelFactory(
			"classpath:org/springframework/statemachine/uml/import-main/import-main.uml",
			new String[] { "classpath:org/springframework/statemachine/uml/import-sub/import-sub.uml" });
	}
}
Links between files in uml models needs to be relative as otherwise things break when model files are copied out from a classpath to a temporary directory so that eclipse parsing classes can read those.

Repository Support

This section contains documentation related to using 'Spring Data Repositories' in Spring Statemachine.spring-doc.cn

Repository Configuration

You can keep machine configuration in external storage, from which it can be loaded on demand, instead of creating a static configuration by using either Java configuration or UML-based configuration. This integration works through a Spring Data Repository abstraction.spring-doc.cn

We have created a special StateMachineModelFactory implementation called RepositoryStateMachineModelFactory. It can use the base repository interfaces (StateRepository, TransitionRepository, ActionRepository and GuardRepository) and base entity interfaces (RepositoryState, RepositoryTransition, RepositoryAction, and RepositoryGuard).spring-doc.cn

Due to way how entities and repositories work in Spring Data, from a user perspective, read access can be fully abstracted as it is done in RepositoryStateMachineModelFactory. There is no need to know the actual mapped entity class with which a repository works. Writing into a repository is always dependent on using a real repository-specific entity class. From a machine-configuration point of view, we do not need to know these, meaning that we do not need to know whether the actual implementation is JPA, Redis, or anything else that Spring Data supports. Using an actual repository-related entity class comes into play when you manually try to write new states or transitions into a backed repository.spring-doc.cn

Entity classes for RepositoryState and RepositoryTransition have a machineId field, which is at your disposal and can be used to differentiate between configurations — for example, if machines are built via StateMachineFactory.

Actual implementations are documented in later sections. The following images are UML-equivalent state charts of repository configurations.spring-doc.cn

sm repository simplemachine
Figure 1. SimpleMachine
sm repository simplesubmachine
Figure 2. SimpleSubMachine
sm repository showcasemachine
Figure 3. ShowcaseMachine

JPA

The actual repository implementations for JPA are JpaStateRepository, JpaTransitionRepository, JpaActionRepository, and JpaGuardRepository, which are backed by the entity classes JpaRepositoryState, JpaRepositoryTransition, JpaRepositoryAction, and JpaRepositoryGuard, respectively.spring-doc.cn

Unfortunately, version '1.2.8' had to make a change in JPA’s entity model regarding used table names. Previously, generated table names always had a prefix of JPA_REPOSITORY_, derived from entity class names. As this caused breaking issues with databases imposing restrictions on database object lengths, all entity classes have spesific definitions to force table names. For example, JPA_REPOSITORY_STATE is now 'STATE' — and so on with other ntity classes.

The generic way to update states and transitions manually for JPA is shown in the following example (equivalent to the machine shown in SimpleMachine):spring-doc.cn

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

void addConfig() {
	JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
	JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
	JpaRepositoryState stateS3 = new JpaRepositoryState("S3");

	stateRepository.save(stateS1);
	stateRepository.save(stateS2);
	stateRepository.save(stateS3);

	JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
	JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS2, stateS3, "E2");

	transitionRepository.save(transitionS1ToS2);
	transitionRepository.save(transitionS2ToS3);
}

The following example is also equivalent to the machine shown in SimpleSubMachine.spring-doc.cn

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

void addConfig() {
	JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
	JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
	JpaRepositoryState stateS3 = new JpaRepositoryState("S3");

	JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
	stateS21.setParentState(stateS2);
	JpaRepositoryState stateS22 = new JpaRepositoryState("S22");
	stateS22.setParentState(stateS2);

	stateRepository.save(stateS1);
	stateRepository.save(stateS2);
	stateRepository.save(stateS3);
	stateRepository.save(stateS21);
	stateRepository.save(stateS22);

	JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
	JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS21, stateS22, "E2");
	JpaRepositoryTransition transitionS21ToS22 = new JpaRepositoryTransition(stateS2, stateS3, "E3");

	transitionRepository.save(transitionS1ToS2);
	transitionRepository.save(transitionS2ToS3);
	transitionRepository.save(transitionS21ToS22);
}

First, you must access all repositories. The following example shows how to do so:spring-doc.cn

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

@Autowired
ActionRepository<JpaRepositoryAction> actionRepository;

@Autowired
GuardRepository<JpaRepositoryGuard> guardRepository;

Second, you mus create actions and guards. The following example shows how to do so:spring-doc.cn

JpaRepositoryGuard foo0Guard = new JpaRepositoryGuard();
foo0Guard.setName("foo0Guard");

JpaRepositoryGuard foo1Guard = new JpaRepositoryGuard();
foo1Guard.setName("foo1Guard");

JpaRepositoryAction fooAction = new JpaRepositoryAction();
fooAction.setName("fooAction");

guardRepository.save(foo0Guard);
guardRepository.save(foo1Guard);
actionRepository.save(fooAction);

Third, you must create states. The following example shows how to do so:spring-doc.cn

JpaRepositoryState stateS0 = new JpaRepositoryState("S0", true);
stateS0.setInitialAction(fooAction);
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
stateS1.setParentState(stateS0);
JpaRepositoryState stateS11 = new JpaRepositoryState("S11", true);
stateS11.setParentState(stateS1);
JpaRepositoryState stateS12 = new JpaRepositoryState("S12");
stateS12.setParentState(stateS1);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
stateS2.setParentState(stateS0);
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS211 = new JpaRepositoryState("S211", true);
stateS211.setParentState(stateS21);
JpaRepositoryState stateS212 = new JpaRepositoryState("S212");
stateS212.setParentState(stateS21);

stateRepository.save(stateS0);
stateRepository.save(stateS1);
stateRepository.save(stateS11);
stateRepository.save(stateS12);
stateRepository.save(stateS2);
stateRepository.save(stateS21);
stateRepository.save(stateS211);
stateRepository.save(stateS212);

Fourth and finally, you must create transitions. The following example shows how to do so:spring-doc.cn

JpaRepositoryTransition transitionS1ToS1 = new JpaRepositoryTransition(stateS1, stateS1, "A");
transitionS1ToS1.setGuard(foo1Guard);

JpaRepositoryTransition transitionS1ToS11 = new JpaRepositoryTransition(stateS1, stateS11, "B");
JpaRepositoryTransition transitionS21ToS211 = new JpaRepositoryTransition(stateS21, stateS211, "B");
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "C");
JpaRepositoryTransition transitionS1ToS0 = new JpaRepositoryTransition(stateS1, stateS0, "D");
JpaRepositoryTransition transitionS211ToS21 = new JpaRepositoryTransition(stateS211, stateS21, "D");
JpaRepositoryTransition transitionS0ToS211 = new JpaRepositoryTransition(stateS0, stateS211, "E");
JpaRepositoryTransition transitionS1ToS211 = new JpaRepositoryTransition(stateS1, stateS211, "F");
JpaRepositoryTransition transitionS2ToS21 = new JpaRepositoryTransition(stateS2, stateS21, "F");
JpaRepositoryTransition transitionS11ToS211 = new JpaRepositoryTransition(stateS11, stateS211, "G");

JpaRepositoryTransition transitionS0 = new JpaRepositoryTransition(stateS0, stateS0, "H");
transitionS0.setKind(TransitionKind.INTERNAL);
transitionS0.setGuard(foo0Guard);
transitionS0.setActions(new HashSet<>(Arrays.asList(fooAction)));

JpaRepositoryTransition transitionS1 = new JpaRepositoryTransition(stateS1, stateS1, "H");
transitionS1.setKind(TransitionKind.INTERNAL);

JpaRepositoryTransition transitionS2 = new JpaRepositoryTransition(stateS2, stateS2, "H");
transitionS2.setKind(TransitionKind.INTERNAL);
transitionS2.setGuard(foo1Guard);
transitionS2.setActions(new HashSet<>(Arrays.asList(fooAction)));

JpaRepositoryTransition transitionS11ToS12 = new JpaRepositoryTransition(stateS11, stateS12, "I");
JpaRepositoryTransition transitionS12ToS212 = new JpaRepositoryTransition(stateS12, stateS212, "I");
JpaRepositoryTransition transitionS211ToS12 = new JpaRepositoryTransition(stateS211, stateS12, "I");

JpaRepositoryTransition transitionS11 = new JpaRepositoryTransition(stateS11, stateS11, "J");
JpaRepositoryTransition transitionS2ToS1 = new JpaRepositoryTransition(stateS2, stateS1, "K");

transitionRepository.save(transitionS1ToS1);
transitionRepository.save(transitionS1ToS11);
transitionRepository.save(transitionS21ToS211);
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS1ToS0);
transitionRepository.save(transitionS211ToS21);
transitionRepository.save(transitionS0ToS211);
transitionRepository.save(transitionS1ToS211);
transitionRepository.save(transitionS2ToS21);
transitionRepository.save(transitionS11ToS211);
transitionRepository.save(transitionS0);
transitionRepository.save(transitionS1);
transitionRepository.save(transitionS2);
transitionRepository.save(transitionS11ToS12);
transitionRepository.save(transitionS12ToS212);
transitionRepository.save(transitionS211ToS12);
transitionRepository.save(transitionS11);
transitionRepository.save(transitionS2ToS1);

You can find a complete example here. This example also shows how you can pre-populate a repository from an existing JSON file that has definitions for entity classes.spring-doc.cn

Redis

The actual repository implementations for a Redis instance are RedisStateRepository, RedisTransitionRepository, RedisActionRepository, and RedisGuardRepository, which are backed by the entity classes RedisRepositoryState, RedisRepositoryTransition, RedisRepositoryAction, and RedisRepositoryGuard, respectively.spring-doc.cn

The next example shows the generic way to manually update states and transitions for Redis. This is equivalent to machine shown in SimpleMachine.spring-doc.cn

@Autowired
StateRepository<RedisRepositoryState> stateRepository;

@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;

void addConfig() {
	RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
	RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
	RedisRepositoryState stateS3 = new RedisRepositoryState("S3");

	stateRepository.save(stateS1);
	stateRepository.save(stateS2);
	stateRepository.save(stateS3);


	RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
	RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");

	transitionRepository.save(transitionS1ToS2);
	transitionRepository.save(transitionS2ToS3);
}

The following example is equivalent to machine shown in SimpleSubMachine:spring-doc.cn

@Autowired
StateRepository<RedisRepositoryState> stateRepository;

@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;

void addConfig() {
	RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
	RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
	RedisRepositoryState stateS3 = new RedisRepositoryState("S3");

	stateRepository.save(stateS1);
	stateRepository.save(stateS2);
	stateRepository.save(stateS3);


	RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
	RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");

	transitionRepository.save(transitionS1ToS2);
	transitionRepository.save(transitionS2ToS3);
}

MongoDB

The actual repository implementations for a MongoDB instance are MongoDbStateRepository, MongoDbTransitionRepository, MongoDbActionRepository, and MongoDbGuardRepository, which are backed by the entity classes MongoDbRepositoryState, MongoDbRepositoryTransition, MongoDbRepositoryAction, and MongoDbRepositoryGuard, respectively.spring-doc.cn

The next example shows the generic way to manually update states and transitions for MongoDB. This is equivalent to the machine shown in SimpleMachine.spring-doc.cn

@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;

@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;

void addConfig() {
	MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
	MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
	MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");

	stateRepository.save(stateS1);
	stateRepository.save(stateS2);
	stateRepository.save(stateS3);

	MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
	MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS2, stateS3, "E2");

	transitionRepository.save(transitionS1ToS2);
	transitionRepository.save(transitionS2ToS3);
}

The following example is equivalent to the machine shown in SimpleSubMachine.spring-doc.cn

@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;

@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;

void addConfig() {
	MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
	MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
	MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");

	MongoDbRepositoryState stateS21 = new MongoDbRepositoryState("S21", true);
	stateS21.setParentState(stateS2);
	MongoDbRepositoryState stateS22 = new MongoDbRepositoryState("S22");
	stateS22.setParentState(stateS2);

	stateRepository.save(stateS1);
	stateRepository.save(stateS2);
	stateRepository.save(stateS3);
	stateRepository.save(stateS21);
	stateRepository.save(stateS22);

	MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
	MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS21, stateS22, "E2");
	MongoDbRepositoryTransition transitionS21ToS22 = new MongoDbRepositoryTransition(stateS2, stateS3, "E3");

	transitionRepository.save(transitionS1ToS2);
	transitionRepository.save(transitionS2ToS3);
	transitionRepository.save(transitionS21ToS22);
}

Repository Persistence

Apart from storing machine configuration (as shown in Repository Configuration), in an external repository, you canx also persist machines into repositories.spring-doc.cn

The StateMachineRepository interface is a central access point that interacts with machine persistence and is backed by the entity class RepositoryStateMachine.spring-doc.cn

JPA

The actual repository implementation for JPA is JpaStateMachineRepository, which is backed by the entity class JpaRepositoryStateMachine.spring-doc.cn

The following example shows the generic way to persist a machine for JPA:spring-doc.cn

@Autowired
StateMachineRepository<JpaRepositoryStateMachine> stateMachineRepository;

void persist() {

	JpaRepositoryStateMachine machine = new JpaRepositoryStateMachine();
	machine.setMachineId("machine");
	machine.setState("S1");
	// raw byte[] representation of a context
	machine.setStateMachineContext(new byte[] { 0 });

	stateMachineRepository.save(machine);
}

Redis

The actual repository implementation for a Redis is RedisStateMachineRepository, which is backed by the entity class RedisRepositoryStateMachine.spring-doc.cn

The following example shows the generic way to persist a machine for Redis:spring-doc.cn

@Autowired
StateMachineRepository<RedisRepositoryStateMachine> stateMachineRepository;

void persist() {

	RedisRepositoryStateMachine machine = new RedisRepositoryStateMachine();
	machine.setMachineId("machine");
	machine.setState("S1");
	// raw byte[] representation of a context
	machine.setStateMachineContext(new byte[] { 0 });

	stateMachineRepository.save(machine);
}

MongoDB

The actual repository implementation for MongoDB is MongoDbStateMachineRepository, which is backed by the entity class MongoDbRepositoryStateMachine.spring-doc.cn

The following example shows the generic way to persist a machine for MongoDB:spring-doc.cn

@Autowired
StateMachineRepository<MongoDbRepositoryStateMachine> stateMachineRepository;

void persist() {

	MongoDbRepositoryStateMachine machine = new MongoDbRepositoryStateMachine();
	machine.setMachineId("machine");
	machine.setState("S1");
	// raw byte[] representation of a context
	machine.setStateMachineContext(new byte[] { 0 });

	stateMachineRepository.save(machine);
}