有限状态机(Finite-state machine)
有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。应用FSM模型可以帮助对象生命周期的状态的顺序以及导致状态变化的事件进行管理。将状态和事件控制从不同的业务Service方法的if else中抽离出来。FSM的应用范围很广,对于有复杂状态流,扩展性要求比较高的场景都可以使用该模型。下面是状态机模型中的4个要素,即现态、条件、动作、次态。
- 现态:是指当前所处的状态。
- 条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
- 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
- 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
如下图示例:有限的状态集是“opend”以及“closed”。如果“现态”是“opend”,当“条件”为“Close”时,执行的“动作”是“close door”,次态则为“closed”。状态机逻辑执行完毕后“closed”则变成了“现态”。
所以FSM的执行逻辑可以理解为下图,即FSM的下一个状态和输出是由输入和当前状态决定的:
集成Spring-statemachine框架到Springboot应用(订单示例)
基于JDK8。Spring statemachine版本:2.0.0,SpringBoot 版本:2.0.3。
step1 配置依赖pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<!-- <version>${servlet-api.version}</version> -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- uml to code -->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-uml</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
step2 状态枚举与事件枚举
public enum States {
UNPAID, // 待支付
WAITING_FOR_RECEIVE, // 待收货
DONE // 结束
}
public enum Events {
PAY, // 支付
RECEIVE // 收货
}
step3 初始化订单的状态集合以及状态转移事件
在启动springboot时,需要注入状态机的状态,事件的配置。主要涉及到以下两个类:
StateMachineStateConfigurer < S, E> 配置状态集合以及初始状态,泛型参数S代表状态,E代表事件。
StateMachineTransitionConfigurer 配置状态流的转移,可以定义状态转换接受的事件。
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
private Logger LOGGER = LoggerFactory.getLogger(getClass());
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states.withStates().initial(States.UNPAID).states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.UNPAID).target(States.WAITING_FOR_RECEIVE)
.event(Events.PAY)
.and()
.withExternal()
.source(States.WAITING_FOR_RECEIVE).target(States.DONE)
.event(Events.RECEIVE);
}
//守护
@Bean
public Guard<States, Events> guard() {
return new Guard<States, Events>() {
public boolean evaluate(StateContext<States, Events> context) {
return true;
}
};
}
step4 状态转移的监听器
状态转移过程中,可以通过监听器(Listener)来处理一些持久化或者业务监控等任务。在需要持久化的场景中,可以在状态机模式中的监听器中添加持久化的处理。
@WithStateMachine
public class EventListener {
private Logger LOGGER = LoggerFactory.getLogger(getClass());
@OnTransition(target = "UNPAID")
public void create() {
//此处可以执行具体业务逻辑处理
LOGGER.info("--------------------订单创建,待支付-----------------------");
}
@OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void pay() {
//此处可以执行具体业务逻辑处理
LOGGER.info("--------------------用户完成支付,待收货--------------------");
}
@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive() {
//此处可以执行具体业务逻辑处理
LOGGER.info("--------------------用户已收货,订单完成----------------------");
}
}
step5 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private StateMachine<States, Events> stateMachine;
@Test
public void test() throws Exception {
stateMachine.start();
stateMachine.sendEvent(Events.PAY);
stateMachine.sendEvent(Events.RECEIVE);
}
}
1.只运行启动
@Test
public void test() throws Exception {
stateMachine.start(); //springboot启动时开启 待支付
//stateMachine.sendEvent(Events.PAY); //支付操作 待收货
//stateMachine.sendEvent(Events.RECEIVE); //收货操作 结束
}
2.运行启动与支付操作
@Test
public void test() throws Exception {
stateMachine.start(); //springboot启动时开启 待支付
stateMachine.sendEvent(Events.PAY); //支付操作 待收货
//stateMachine.sendEvent(Events.RECEIVE); //收货操作 结束
}
3.全部执行
@Test
public void test() throws Exception {
stateMachine.start(); //springboot启动时开启 待支付
stateMachine.sendEvent(Events.PAY); //支付操作 待收货
stateMachine.sendEvent(Events.RECEIVE); //收货操作 结束
}