背景介紹
本系列通過學習SpringStateMachine中附帶的10餘個Sample來學習SpringStateMachine中的各個概念和用法。項目是使用的分支爲2.2.0.RELEASE[1]。項目參考文檔也是2.2.0.RELEASE[1]。
TURN STILE簡介
turnstile是對體育場入口或地鐵入口的旋轉柵門構建的狀態機。
狀態機的兩種狀態:
- LOCKED
- UNLOCKED
狀態機的兩種事件
- COIN
- PUSH
觸發相應事件後發生狀態轉換。
TURN STILE 依賴
項目在實現上述功能時,需要依賴springshell,官方給出的demo[4]使用了spring-shell1.2,本文將其改爲spring-shell 2.0.0.RELEASE。
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
TURN STILE 實現
爲了實現本例,我們需要描述狀態及其轉換。首先定義狀態與事件枚舉類型。
狀態枚舉類型:
public enum States {
LOCKED, UNLOCKED
}
使狀態發生變化的事件枚舉類型:
public enum Events {
COIN, PUSH
}
接着我們配置狀態與轉換。
package springboot.statemachine.example.turnstile.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import java.util.EnumSet;
@Configuration
@EnableStateMachine
public class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.LOCKED)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.LOCKED)
.target(States.UNLOCKED)
.event(Events.COIN)
.and()
.withExternal()
.source(States.UNLOCKED)
.target(States.LOCKED)
.event(Events.PUSH);
}
}
在配置狀態與轉換時用到如下的註解與類:
- @Configuration
- @EnableStateMachine
- EnumStateMachineConfigurerAdapter
代碼繼承EnumStateMachineConfigurerAdapter類,並覆蓋兩個configure方法,在這兩個方法中分別來配置轉換與狀態列表同時指定好初始狀態。
隨後在創建的類上增加@EnableStateMachine與@Configuration註解,通過這些創建狀態機實例,系統隨後會檢測是否使用了adapter類,並在運行時根據這些配置修改狀態機。
statemachine中有三種形式的轉換(transition)external, internal, local。
這裏我們使用了withExternal,返回一個ExternalTransitionConfigurer來完成轉換的配置。target方法指定了目標狀態,source指定了源狀態,event指定了使狀態發生變更的事件。
最後是命令實現。這裏對StateMachineCommands官方demo[5]進行了小修改。最後通過stateMachine的sendEvent方法發送事件,使狀態機狀態發生變化。
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import springboot.statemachine.example.AbstractStateMachineCommands;
@ShellComponent
public class StateMachineCommands extends AbstractStateMachineCommands<States, Events> {
@ShellMethod(key = "sm event", value = "Sends an event to a state machine")
public String event(Events event) {
getStateMachine().sendEvent(event);
return "Event " + event + " send";
}
}
此外將官方AbstractStateMachineCommands[6]中打印turn stile字符圖形的部分去掉了。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.util.StringUtils;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class AbstractStateMachineCommands<S, E>{
@Autowired
private StateMachine<S, E> stateMachine;
protected StateMachine<S, E> getStateMachine() {
return stateMachine;
}
@ShellMethod(key = "sm state", value = "Prints current state")
public String state() {
State<S, E> state = stateMachine.getState();
if (state != null) {
return StringUtils.collectionToCommaDelimitedString(state.getIds());
} else {
return "No state";
}
}
@ShellMethod(key = "sm start", value = "Start a state machine")
public String start() {
stateMachine.start();
return "State machine started";
}
@ShellMethod(key = "sm stop", value = "Stop a state machine")
public String stop() {
stateMachine.stop();
return "State machine stopped";
}
@ShellMethod(key = "sm variables", value = "Prints extended state variables")
public String variables() {
StringBuilder buf = new StringBuilder();
Set<Entry<Object, Object>> entrySet = stateMachine.getExtendedState().getVariables().entrySet();
Iterator<Entry<Object, Object>> iterator = entrySet.iterator();
if (entrySet.size() > 0) {
while (iterator.hasNext()) {
Entry<Object, Object> e = iterator.next();
buf.append(e.getKey() + "=" + e.getValue());
if (iterator.hasNext()) {
buf.append("\n");
}
}
} else {
buf.append("No variables");
}
return buf.toString();
}
}
驗證
發送不同的命令觸發事件,並查看狀態機當前狀態,發現狀態正確發生變化。
State machine started
shell:>sm event COIN
Event COIN send
shell:>sm event PUSH
Event PUSH send
shell:>sm start
State machine started
shell:>sm state
LOCKED
shell:>sm event COIN
Event COIN send
shell:>sm state
UNLOCKED
shell:>sm event PUSH
Event PUSH send
shell:>sm state
LOCKED
shell:>sm event PUSH
Event PUSH send
shell:>sm state
LOCKED
shell:>sm state COIN
LOCKED
shell:>sm state
LOCKED
shell:>sm start
State machine started
shell:>sm state
LOCKED
shell:>sm stop
總結
通過turnstile這個例子學習了基礎的概念及相關配置。通過
- @Configuration,
- @EnableStateMachine,
- EnumStateMachineConfigurerAdapter
完成配置及創建單實例。實現觸發事件使狀態機當前狀態發生變化,從源狀態到目標狀態變化。
參考
[1]2.2.0.RELEASE source,https://github.com/spring-projects/spring-statemachine/blob/2.2.0.RELEASE/
[2]2.2.0.RELEASE/reference,https://docs.spring.io/spring-statemachine/docs/2.2.0.RELEASE/reference
[3]turnstile,https://docs.spring.io/spring-statemachine/docs/2.2.0.RELEASE/reference/#statemachine-examples-turnstile
[4]turnstile demo,https://github.com/spring-projects/spring-statemachine/tree/2.2.0.RELEASE/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile
[5]StateMachineCommands,https://github.com/spring-projects/spring-statemachine/blob/2.2.0.RELEASE/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java
[6]AbstractStateMachineCommands,https://github.com/spring-projects/spring-statemachine/blob/2.2.0.RELEASE/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java