天天看點

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);
    }
}      
運作代碼,可以看見如下輸出: