背景介绍
本系列通过学习SpringStateMachine中附带的10余个Sample来学习SpringStateMachine中的各个概念和用法。项目是使用的分支为2.2.0.RELEASE[1]。项目参考文档也是2.2.0.RELEASE[1]。
Showcase简介
showcase这个例子演示的是一个分层有限状态机(HFSM)[1]。该例中的状态机共分4层,并且引入了action、guard、internal transition等工具来构建分层有限状态机。同时演示了事件发生时触发action及通过guard控制action及状态转换的发生。该状态机如下图所示。
Showcase 依赖
项目在实现上述功能时,需要依赖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>
Showcase 实现
showcase这个demo使用了如下几项新功能[2](第一个demo未出现):
- action: 用来自定义一个方法,当事件发生后会执行该方法。
- guard: 用来自定义一个条件。当满足条件,执行转换或action否则不执行相关操作。
- extendedState: 自定义的一些额外状态,运来进行运行时进行额外判断。
- internal transistion: 仅用来触发action执行而不发生转换。source state和target state保持一致。
States
public enum States {
S0, S1, S11, S12, S2, S21, S211, S212
}
Events
public enum Events {
A, B, C, D, E, F, G, H, I
}
根据状态图,层级结构中各个状态关系如下:
S0有2个子状态S1,S2
S1有两个子状态 S11 S12
S2有一个子状态 S21
S21 有两个子状态 s211 s212
S21 有两个子状态s211和s212
如上的概念明确后,和turnStile一样,我们完成相关配置即可。这个demo只是会多处action,guard及子状态配置。
配置状态时我们通过parent方法指定一个状态的父状态。
在配置转换关系时,我们指定了guard和action。当触发事件后先要检查guard中设定的条件是否满足,如果满足才会执行状态转换及action。
通过withInternal指定了internal transition,这样事件触发仅仅因此action执行。
import org.springframework.context.annotation.Bean;
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;
@Configuration
@EnableStateMachine
public class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S0, fooAction())
.state(States.S0)
.and()
.withStates()
.parent(States.S0)
.initial(States.S1)
.state(States.S1)
.and()
.withStates()
.parent(States.S1)
.initial(States.S11)
.state(States.S11)
.state(States.S12)
.and()
.withStates()
.parent(States.S0)
.state(States.S2)
.and()
.withStates()
.parent(States.S2)
.initial(States.S21)
.state(States.S21)
.and()
.withStates()
.parent(States.S21)
.initial(States.S211)
.state(States.S211)
.state(States.S212);
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.S1).target(States.S1).event(Events.A)
.guard(foo1Guard())
.and()
.withExternal()
.source(States.S1).target(States.S11).event(Events.B)
.and()
.withExternal()
.source(States.S21).target(States.S211).event(Events.B)
.and()
.withExternal()
.source(States.S1).target(States.S2).event(Events.C)
.and()
.withExternal()
.source(States.S2).target(States.S1).event(Events.C)
.and()
.withExternal()
.source(States.S1).target(States.S0).event(Events.D)
.and()
.withExternal()
.source(States.S211).target(States.S21).event(Events.D)
.and()
.withExternal()
.source(States.S0).target(States.S211).event(Events.E)
.and()
.withExternal()
.source(States.S1).target(States.S211).event(Events.F)
.and()
.withExternal()
.source(States.S2).target(States.S11).event(Events.F)
.and()
.withExternal()
.source(States.S11).target(States.S211).event(Events.G)
.and()
.withExternal()
.source(States.S211).target(States.S0).event(Events.G)
.and()
.withInternal()
.source(States.S0).event(Events.H)
.guard(foo0Guard())
.action(fooAction())
.and()
.withInternal()
.source(States.S2).event(Events.H)
.guard(foo1Guard())
.action(fooAction())
.and()
.withInternal()
.source(States.S1).event(Events.H)
.and()
.withExternal()
.source(States.S11).target(States.S12).event(Events.I)
.and()
.withExternal()
.source(States.S211).target(States.S212).event(Events.I)
.and()
.withExternal()
.source(States.S12).target(States.S212).event(Events.I);
}
@Bean
FooAction fooAction() {
return new FooAction();
}
@Bean
public FooGuard foo0Guard() {
return new FooGuard(0);
}
@Bean
public FooGuard foo1Guard() {
return new FooGuard(1);
}
}
FooAction中演示了extendedState的使用,这里触发action后,我们增加了一些自定义变量。随后作为guard中的判断条件使用。
public class FooAction implements Action<States,Events> {
private final static Log log = LogFactory.getLog(FooAction.class);
@Override
public void execute(StateContext<States, Events> context) {
Map<Object, Object> variables = context.getExtendedState().getVariables();
Integer foo = context.getExtendedState().get("foo", Integer.class);
if (foo == null) {
log.info("Init foo to 0");
variables.put("foo", 0);
} else if (foo == 0) {
log.info("Switch foo to 1");
variables.put("foo", 1);
} else if (foo == 1) {
log.info("Switch foo to 0");
variables.put("foo", 0);
}
}
}
FooGuard,使用了action中在extendedState中增加的条件完成判断。
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.guard.Guard;
/**
* Created on 2020/2/9.
*/
public class FooGuard implements Guard<States,Events> {
private final int match;
public FooGuard(int match) {
this.match = match;
}
@Override
public boolean evaluate(StateContext<States, Events> context) {
Object foo = context.getExtendedState().getVariables().get("foo");
return (foo == null || !foo.equals(match));
}
}
StateMachineCommands与turnStile中一致。
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也与turnStile一致完成相应的命令执行。
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();
}
}
总结
通过Showcase学习到了层次有限状态机(HFSM)的概念与配置。在事件触发时候使用Action来完成自定义操作,通过Guard来自定义Action和transition触发的条件,最后是通过internal transition来实现仅触发Action而不需要执行transistion的能力。
参考
[1]HFSM,https://gameinstitute.qq.com/community/detail/110991
[2]https://docs.spring.io/spring-statemachine/docs/2.2.0.RELEASE/reference/#actions