目录
1. 简单介绍状态机
2. 状态机的本质
3. 状态机应用场景
配置pom依赖:
定义状态枚举和事件枚举:
完成状态机配置:状态机的初始状态和所有状态机的转移规则
创建应用主类来完成整个流程:
注解监听器
1. 简单介绍状态机
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。以上是百度百科对状态机的解释。
在百科的解释中,我们可以提炼出状态机的几个要素:存储状态,逻辑电路,预先设定的状态转移路径,外部来的信号,内置的特定操作等。由这些关键要素我们可以推断出以下几点:
-
状态数据有预先设定的多种值
-
逻辑电路是完成内置特定操作的基础“代码“(基础设施),由工程司开发
-
状态能在多种值之间转移
-
每次转移都由外部信号触发
-
状态转移触发后,会有相应的内置操作
由以上这些特性共同构成了一个独立的控制中心,而且这个控制中心与外部各种信号是低耦合的,所有外部信号都要接入一个共同的控制中心,最终也由控制中心完成信号的后续流程。
在java开发中,状态机FSM(有限状态机)是一种常见的设计模式,常常用于一些复杂的业务场景,解决繁琐杂乱的if…else…代码段。
“Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.“
-对象内部状态不同会有不同的行为。似乎好像是不同的类一样。
以电梯为例,电梯有4种状态:开门、关门、运行、停止。电梯的动作:开门、关门、运行(上升或下降)、停止(悬停不动)。
Col1 | 开门行为 | 关门行为 | 运行行为 | 停止行为 |
---|---|---|---|---|
开门 态 | ❌ | ✔ | ❌ | ❌ |
关门 态 | ✔ | ❌ | ✔ | ✔ |
运行 态 | ❌ | ❌ | ❌ | ✔ |
停止 态 | ✔ | ❌ | ✔ | ❌ |
-
开门状态下,电梯只有关门行为
-
关门状态下,除了关门行为其他行为都有
-
运行状态下,只有停止行为
-
停止状态下,有开门行为和运行行为
状态图如下:
状态机设计模式的好处:
-
降低程序的复杂度;
-
提高程序的可维护性;
-
状态机模式体现了开闭原则和单一职责原则。
-
每个状态都是一个子类,增加状态就要增加子类;修改状态只要修改一个类就行了。
这种设计模式将每个状态的变更后的处理逻辑都做了统一封装,跟业务代码耦合,只接收相互约定的信号(事件)。
2. 状态机的本质
由状态机的几个要素我们也可以知道,在一个复杂的业务流程过程中,有多种数据状态,多种处理动作,以及多种维度的角色。在这样复杂业务场景下,如果只是简单使用if…else…语句,首先可读性就非常差,而且维护起来非常困难,甚至是开发人员都没办法理清楚自己写的if..else语句。如果复杂的业务流程有了变更和新增,那这个维护起来就是个灾难,各种复杂问题不得而知。就像下图一样
用if..else..代码来开发复杂的业务流程,会面临下面几个问题:
-
复杂的业务流程,if.else代码几乎无法维护
-
随着业务的发展,业务过程也需要变更及扩展,但if.else代码段已经无法支持
-
没有可读性,变更风险特别大,可能会牵一发而动全身,给服务带来S级风险。
-
其他业务逻辑可能也会跟if.else代码块耦合在一起,带来更多的问题。
这时候,状态机就是个非常好的解决方案,能有效地解决这些问题。
一个状态机定义以后,在某个状态下就只接收固定的Event,也就是执行指定的操作,这样流程就能按照预期定义的那样流转,不会出现乱入的情况,执行了一些在某状态下不允许执行的操作,也就是说,状态的流转都是在控制范围,不会超出预设的状态空间。
-
状态机建立的控制中心是跟外界低耦合的,通过event通信。
-
控制中心所有的状态都是预设好的,不会超预料。
-
状态的跳转都是有设定控制条件的,会按照预设的转移路径运动。
-
状态机还非常容易的扩展和变更,支持因业务的发展而变更或扩展复杂业务流程
3. 状态机应用场景
状态机典型的应用有工作流引擎,游戏中人物状态变化引擎,订单交易等。前两种非常复杂,下面我就简单以订单交易这个场景来聊聊状态机的应用场景以及基于spring statemachine的项目。
配置pom依赖:
org.springframework.statemachine
spring-statemachine-core
2.1.2.RELEASE
定义状态枚举和事件枚举:
public enum States {
UNPAID, // 待支付
WAITING_FOR_RECEIVE, // 待收货
DONE // 结束
}
public enum Events {
PAY, // 支付
RECEIVE // 收货
}
完成状态机配置:状态机的初始状态和所有状态机的转移规则
@Configuration
//启用状态机功能
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void configure(StateMachineStateConfigurer states) throws Exception{
//定义状态机中的状态
states.withStates()
.initial(States.UNPAID) //初始状态
.states(EnumSet.allOf(States.class));//指定使用上一步定义的所有状态作为该状态机的状态定义
}
@Override
public void configure(StateMachineTransitionConfigurer 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);
}
@Override
public void configure(StateMachineConfigurationConfigurer config) throws Exception{
config
.withConfiguration()
.listener(listener()); //指定状态机的处理监听器
}
//创建状态监听器的实例,定义具体的状态迁移处理逻辑,在通常情况将该实例的定义放到独立的类定义中,用注入方式加载进来
@Bean
public StateMachineListener listener(){
return new StateMachineListenerAdapter(){
@Override
public void transition(Transition transition){
if(transition.getTarget().getId() == States.UNPAID){
logger.info("订单创建,待支付");
return;
}
if(transition.getSource().getId() == States.UNPAID
&& transition.getTarget().getId() == States.WAITING_FOR_RECEIVE){
logger.info("用户完成支付,待收货");
return;
}
if(transition.getSource().getId() == States.WAITING_FOR_RECEIVE
&& transition.getTarget().getId() == States.DONE){
logger.info("用户已收货,订单完成");
}
}
};
}
}
创建应用主类来完成整个流程:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.statemachine.StateMachine;
@SpringBootApplication
public class Application implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Autowired
private StateMachine stateMachine;
@Override
public void run(String... args){
stateMachine.start();
stateMachine.sendEvent(Events.PAY);
stateMachine.sendEvent(Events.RECEIVE);
}
}
整个状态的调度逻辑主要依靠配置方式的定义,而所有的业务逻辑操作都被定义在了状态监听器中,其实状态监听器可以实现的功能远不止上面我们所述的内容,它还有更多的事件捕获
注解监听器
对于状态监听器,Spring StateMachine还提供了优雅的注解配置实现方式,所有StateMachineListener接口中定义的事件都能通过注解的方式来进行配置实现。比如,我们可以将之前实现的状态监听器用注解配置来做进一步的简化。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.OnTransitionEnd;
import org.springframework.statemachine.annotation.OnTransitionStart;
import org.springframework.statemachine.annotation.WithStateMachine;
/**
* 该配置实现了com.lyd.StateMachineConfig类中定义的状态机监听器实现。
*/
@WithStateMachine
public class EventConfig {
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("用户完成支付,待收货");
}
@OnTransitionStart(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void payStart() {
logger.info("用户完成支付,待收货: start");
}
@OnTransitionEnd(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void payEnd() {
logger.info("用户完成支付,待收货: end");
}
@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive() {
logger.info("用户已收货,订单完成");
}
}
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
相关推荐: Apache DolphinScheduler 发布 3.1.7 版本,修复 SeaTunnel 任务保存错误
近日,Apache DolphinScheduler 发布了 3.1.7 版本。此版本主要基于 3.1.6 版本进行了 bug 修复,共计修复 7 个 bug, 优化 1 个 doc。 其中的较为重要的为: SeaTunnel 任务保存错误 #14129 任务…