From https://www.jianshu.com/p/9ee887e045dd , thank you
1 Finite-state machine
1.1 definition of state machine
FSM (finite state machine, FSM), also known as FSM, is a mathematical model that represents finite states, their transitions and actions.
Finite state machine embodies two points: first discrete, then finite.
- State: the word state is somewhat difficult to define. State stores information about the past, which means it reflects the input changes from the beginning of the system to the present.
- Actions & transitions: transitions indicate state changes and are described by conditions that must be met to ensure that the transition occurs. An action is a description of the activity to be performed at a given time.
- Guards: the reason why the detector appears is to detect whether the conditions for switching from one state to another are met.
- Event: event, see also event. Generally speaking, something important to the system is called event.
1.2 state machine example
Real world example: ticket gate, from wiki
The revolving door used to control the subway and amusement park facilities is a door, with three revolving arms at the waist height and a passage across the entrance. Initially, the arm was locked, blocking the entrance and preventing customers from passing through. Storing coins or tokens in a slot on the revolving door unlocks the arm and allows a single customer to pass through. After the customer passes, lock the arm again until another coin is inserted.
The revolving door is regarded as a state machine, and there are two possible states: locking and unlocking. There are two inputs that can affect its status: place the coin in the slot (coin) and push the arm (push). In the Locked state, pushing the arm is invalid; it is Locked no matter how many times it is pushed. Input coins - that is, input coins to the machine - to change the status from Locked to unlocked. In the unlocked state, adding extra coins is invalid; that is, giving extra coins input does not change the state. However, the customer pushes the arm, performs push input, and turns the state back to Locked.
The revolving door state machine can be represented by the state conversion table, displaying each possible state, the conversion between them (based on the input given to the machine) and the output generated by each input:
Current State | Input | Next State | Output |
---|---|---|---|
Locked | coin | Unlocked | Unlock the revolving door so that visitors can pass |
Locked | push | Locked | None |
Unlocked | coin | Unlocked | None |
Unlocked | push | Locked | When visitors pass, lock the revolving door |
The rotating gate state machine can also be represented by a directed graph called a state graph (above). Each state is represented by a node (circle). The edge (arrow) shows the transition from one state to another. Each arrow is marked with the input that triggers the transition. Inputs that do not cause a state change (such as coin inputs that are unlocked) are represented by circular arrows that return to the original state. The arrow entering the Locked node from the black dot indicates that it is the initial state.
state diagram
2 Spring Statemachine
2.1 positioning and characteristics
Spring state machine is a framework for application developers to use state machine concepts with spring applications.
Spring Statemachine provides the following features:
- Easy to use flat one level state machine for simple use cases
- Hierarchical state machine structure to ease complex state configuration
- State machine regions to provide even more complex state configurations
- Usage of triggers, transitions, guards and actions
- Type safe configuration adapter
- Builder pattern for easy instance for use outside of Spring Application context
- Recipes for usual use cases
- Distributed state machine based on a Zookeeper State machine event listeners
- UML Eclipse Papyrus modeling
- Store machine config in a persistent storage
- Spring IOC integration to associate bean s with a state machine
2.2 development and community
Related resources:
- Spring Statemachine home page: Home
- Spring Statemachine Github: Github,Star: 500+ & Fork: 200+ (201809)
- Spring Statemachine Gitter: Gitter, less than 100 people
Release:
Latest version: v2.0.2.RELEASE And v1.2.12.RELEASE It has been released 44 times. Please check Github homepage for more details and latest information.
3 function example
3.1 basic functions
Continue to rotate the real example of the door and add Maven dependency. The version used in this example is as follows:
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>2.0.2.RELEASE</version> </dependency>
Define the state of revolving door: lock, unlock, use enumeration
public enum TurnstileStates { Unlocked, Locked }
Define revolving door operation event: push door and coin, use enumeration
public enum TurnstileEvents { COIN, PUSH }
State machine configuration, in which turnstileUnlock() and customerPassAndLock() are the extended business operations after the current state change, which can be modified according to the actual business scenario
@Configuration @EnableStateMachine public class StatemachineConfigurer extends EnumStateMachineConfigurerAdapter<TurnstileStates, TurnstileEvents> { @Override public void configure(StateMachineStateConfigurer<TurnstileStates, TurnstileEvents> states) throws Exception { states .withStates() // Initial state: Locked .initial(TurnstileStates.Locked) .states(EnumSet.allOf(TurnstileStates.class)); } @Override public void configure(StateMachineTransitionConfigurer<TurnstileStates, TurnstileEvents> transitions) throws Exception { transitions .withExternal() .source(TurnstileStates.Unlocked).target(TurnstileStates.Locked) .event(TurnstileEvents.COIN).action(customerPassAndLock()) .and() .withExternal() .source(TurnstileStates.Locked).target(TurnstileStates.Unlocked) .event(TurnstileEvents.PUSH).action(turnstileUnlock()) ; } @Override public void configure(StateMachineConfigurationConfigurer<TurnstileStates, TurnstileEvents> config) throws Exception { config.withConfiguration() .machineId("turnstileStateMachine") ; } public Action<TurnstileStates, TurnstileEvents> turnstileUnlock() { return context -> System.out.println("Unlock the revolving door so that visitors can pass" ); } public Action<TurnstileStates, TurnstileEvents> customerPassAndLock() { return context -> System.out.println("When visitors pass, lock the revolving door" ); } }
Startup classes and test cases
@SpringBootApplication public class StatemachineApplication implements CommandLineRunner { @Autowired private StateMachine<TurnstileStates, TurnstileEvents> stateMachine; public static void main(String[] args) { SpringApplication.run(StatemachineApplication.class, args); } @Override public void run(String... strings) throws Exception { stateMachine.start(); System.out.println("--- coin ---"); stateMachine.sendEvent(TurnstileEvents.COIN); System.out.println("--- coin ---"); stateMachine.sendEvent(TurnstileEvents.COIN); System.out.println("--- push ---"); stateMachine.sendEvent(TurnstileEvents.PUSH); System.out.println("--- push ---"); stateMachine.sendEvent(TurnstileEvents.PUSH); stateMachine.stop(); } }
The result output is consistent with that described by the state machine in the morning.
--- push --- Unlock the revolving door so that visitors can pass --- push --- --- coin --- When visitors pass, lock the revolving door --- coin ---
3.2 practical functions
3.2.1 state storage
State machine persistence. In the actual environment, the current state is often obtained in real time from the persistence media. Spring Statemachine implements StateMachinePersist interface, write and read the state of the current state machine
In this case, HashMap is used as the simulation storage medium, and the real state acquisition method is needed in the formal project
@Component public class BizStateMachinePersist implements StateMachinePersist<TurnstileStates, TurnstileEvents, Integer> { static Map<Integer, TurnstileStates> cache = new HashMap<>(16); @Override public void write(StateMachineContext<TurnstileStates, TurnstileEvents> stateMachineContext, Integer integer) throws Exception { cache.put(integer, stateMachineContext.getState()); } @Override public StateMachineContext<TurnstileStates, TurnstileEvents> read(Integer integer) throws Exception { // Note that the initial state of the state machine is consistent with that defined in the configuration return cache.containsKey(integer) ? new DefaultStateMachineContext<>(cache.get(integer), null, null, null, null, "turnstileStateMachine") : new DefaultStateMachineContext<>(TurnstileStates.Locked, null, null, null, null, "turnstileStateMachine"); } }
Published in StatemachineConfigurer
@Autowired private BizStateMachinePersist bizStateMachinePersist; @Bean public StateMachinePersister<TurnstileStates, TurnstileEvents, Integer> stateMachinePersist() { return new DefaultStateMachinePersister<>(bizStateMachinePersist); }
Used in StatemachineApplication to automatically inject StateMachinePersister object. The test case is as follows
stateMachine.start(); stateMachinePersist.restore(stateMachine, 1); System.out.println("--- push ---"); stateMachine.sendEvent(TurnstileEvents.PUSH); stateMachinePersist.persist(stateMachine, 1); stateMachinePersist.restore(stateMachine, 1); System.out.println("--- push ---"); stateMachine.sendEvent(TurnstileEvents.PUSH); stateMachinePersist.persist(stateMachine, 1); stateMachinePersist.restore(stateMachine, 1); System.out.println("--- coin ---"); stateMachine.sendEvent(TurnstileEvents.COIN); stateMachinePersist.persist(stateMachine, 1); stateMachinePersist.restore(stateMachine, 1); System.out.println("--- coin ---"); stateMachine.sendEvent(TurnstileEvents.COIN); stateMachinePersist.persist(stateMachine, 1); stateMachine.stop();
3.2.2 action monitoring
Define action monitoring class, StatemachineMonitor (name optional), add annotation @ WithStateMachine. In this example, id is used to bind the state machine. According to the document definition, name and id can be used to bind the state machine instance to be monitored. If no name or id is defined, the default listening name is stateMachine.
@WithStateMachine(id = "turnstileStateMachine") public class StatemachineMonitor { @OnTransition public void anyTransition() { System.out.println("--- OnTransition --- init"); } @OnTransition(target = "Unlocked") public void toState1() { System.out.println("--- OnTransition --- toState1"); } @OnStateChanged(source = "Unlocked") public void fromState1() { System.out.println("--- OnTransition --- fromState1"); } }
Event monitoring of other contexts, described in subsequent articles, Official website link
3.2.2 state machine engineering
In the actual business environment, multithreading is often used to process the state corresponding to different business IDs. In the state machine, using the context of events to transfer data will cause multithreading problems. It is necessary to use the state machine engineering to create different state machines with UUID.
In the StatemachineConfigurer class, change @ EnableStateMachine to @ EnableStateMachineFactory, and add state machine processing action encapsulation method. Readers can customize according to business scenarios. This example is a feasible solution
@Service public class StatemachineService { @Autowired private StateMachinePersister<TurnstileStates, TurnstileEvents, Integer> stateMachinePersist; @Autowired private StateMachineFactory<TurnstileStates, TurnstileEvents> stateMachineFactory; public void execute(Integer businessId, TurnstileEvents event, Map<String, Object> context) { // The state machine is created with the tag ID, which is not bound to the specific definition state machine StateMachine<TurnstileStates, TurnstileEvents> stateMachine = stateMachineFactory.getStateMachine(UUID.randomUUID()); stateMachine.start(); try { // During restore of BizStateMachinePersist, bind turnstileStateMachine state machine to listen for related events stateMachinePersist.restore(stateMachine, businessId); // This method is tedious. In fact, map < string and Object > context are injected into message MessageBuilder<TurnstileEvents> messageBuilder = MessageBuilder .withPayload(event) .setHeader("BusinessId", businessId); if (context != null) { context.entrySet().forEach(p -> messageBuilder.setHeader(p.getKey(), p.getValue())); } Message<TurnstileEvents> message = messageBuilder.build(); // Send event to return whether the execution is successful boolean success = stateMachine.sendEvent(message); if (success) { stateMachinePersist.persist(stateMachine, businessId); } else { System.out.println("State machine processing did not succeed. Please process, ID: " + businessId + ",current context: " + context); } } catch (Exception e) { e.printStackTrace(); } finally { stateMachine.stop(); } } }
For a complete example, see: link