天天看点

springboot 有限状态机入门指南

为什么要使用状态机?

如果你写过很复杂的流程系统,流程系统中涉及多步操作,流程达到不同的状态需要有不同的处理,同时状态间的转换也是有特定逻辑的。如果不使用状态机,那么你的代码我估计会有大量的if判断语句,你得判断某个操作指令过来了,当前这个状态是否能执行该指令。当程序需要异常终止,你需要在本不该对数据库进行操作的状态对数据库进行操作,进而导致到处都在操作数据库,一旦数据库出现数据异常,很难确定时哪里出现了问题。给修改bug带来了极大的困难。如果你有上面的困惑,那么我建议你修改代码模式,如果你的代码中存在大量的if elseif,你可以用反射,也可以用策略模式或者状态模式。如果你的系统中很明显就是一个状态流传的逻辑,那么我强烈建议你使用有限状态机重构自己的代码。有限状态机可以解决上面的问题。

什么是状态机?

从我的实际经验来讲,有限状态机可以看作是有向有环图。有向,因为状态一般是以一个状态到另一个状态;有环,因为流程很有可能出现失败然后重新定位到前面某个状态节点。如下所示:

springboot 有限状态机入门指南

为什么说有限状态机能解决去掉大量if判断的问题?

在有限自动机中,状态的转换被预先设定好。以上图为例,按照流程化的编写代码,原本T1状态是只能转移到T2状态的,如果此时来了一个Command要求当前状态转为S2状态,传统的写法只能是加判断条件,判断该指令是否合法,满足我业务流程,不满足就抛弃。这样就使得代码中会嵌入大量的状态-指令契合的条件判断语句。如果使用有限状态机,预先定义好状态之间的转化流程,当一个非法命令来到某个状态的执行部分,当前状态并没有为该命令定义处理方法,那么该命令自然就被舍弃掉了。在流程比较复杂的系统中,这样的优势更为明显。

有限状态机的实现原理是什么呢?

可以利用栈这种数据结构手写一个有限状态机。通过把状态的入栈和出栈操作定义为状态的转化过程,栈顶状态为当前自动机状态流程的状态,通过压栈和出栈的方式实现有限自动机的流程控制。不过,手写的有限状态机要上生产线还是比较麻烦,例如如何解决状态持久化问题等等。

我使用的是springboot,有现成的框架供我使用吗?

答案是肯定的。springboot为spring使用开发人员提供了StateMachine这个有限状态自动机,​​​官方文档传送门​​

接下来让我详细为大家介绍一下如何简单上手StateMachine(详细使用参照官方文档)

引入maven依赖
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>      
定义状态和事件的枚举类型
public enum Events {
    EVENT1, EVENT2
}

public enum States {
    SI, STATE1, STATE2
}      
自定义配置文件
@Configuration
@EnableStateMachine
@Slf4j
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {

    @Bean
    public StateMachineListener<States,Events> listener() {
        return new StateMachineListenerAdapter<States,Events>(){
            @Override
            public void stateChanged(State<States,Events> from,State<States,Events> to){
                log.info("State change to {}",to.getId());
            }
        };
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<States,Events> config) throws Exception {
        config.withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<States,Events> states) throws Exception {
        states.withStates()
                .initial(States.SI)
                .states(EnumSet.allOf(States.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States,Events> transitions) throws Exception {
        transitions
                .withExternal().source(States.SI).target(States.STATE1).event(Events.EVENT1).action(action())
                .and()
                .withExternal().source(States.STATE1).target(States.STATE2).event(Events.EVENT2).action(action());
    }

    @Bean
    public Action<States,Events> action(){
        return new Action<States, Events>() {
            @Override
            public void execute(StateContext<States, Events> stateContext) {
                log.info("from {} to {}",stateContext.getSource().getId(),stateContext.getTarget().getId());
            }
        };
    }
}      
修改Application主类,实现CommandLineRunner接口
@EnableAutoConfiguration
@Configuration
@ComponentScan({"com.chaojilaji.workorderservice"})
public class WorkorderserviceApplication implements CommandLineRunner {


    @Autowired
    private StateMachine<States, Events> stateMachine;

    public static void main(String[] args) {
        SpringApplication.run(WorkorderserviceApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        stateMachine.sendEvent(Events.EVENT1);
        stateMachine.sendEvent(Events.EVENT2);
    }
}      
运行代码,可以看见如下输出: