天天看點

Spring有限狀态機實作原理與機制

一、概念

Spring Statemachine 是應用程式開發人員在 Spring 應用程式中使用狀态機概念的架構,從設計層面分析:狀态機目的是解決複雜的狀态管理流程,保證程式單一原則和開閉原則;業務角度分析:狀态機應有初始化狀态、加載所有已有的狀态、事件、轉移、動作、觸發下一個狀态為驅動,并解決了業務邏輯與狀态管理之間的強耦合。

二、 Spring Statemachine 旨在提供以下功能:

  • 易于使用的平面一級狀态機,适用于簡單的用例。
  • 分層狀态機結構以簡化複雜的狀态配置。
  • 狀态機區域提供更複雜的狀态配置。
  • 觸發器、轉換、守衛和動作的使用。
  • 類型安全配置擴充卡。
  • 用于在 Spring 應用程式上下文之外輕松執行個體化的建構器模式
  • 通常用例的食譜
  • 基于 Zookeeper 的分布式狀态機
  • 狀态機事件偵聽器。
  • UML Eclipse Papyrus 模組化。
  • 将機器配置存儲在持久存儲中。
  • Spring IOC 內建将 bean 與狀态機相關聯。

狀态機很強大,因為行為總是被保證一緻,使得調試相對容易。這是因為操作規則是在機器啟動時一成不變的。這個想法是您的應用程式可能存在于有限數量的狀态中,并且某些預定義的觸發器可以将您的應用程式從一個狀态帶到下一個狀态。此類觸發器可以基于事件或計時器。

在應用程式之外定義進階邏輯然後依靠狀态機來管理狀态要容易得多。您可以通過發送事件、監聽更改或簡單地請求目前狀态來與狀态機互動。

三、狀态機應用場景

使用之前引入spring-statemachine依賴:

<dependency>
     <groupId>org.springframework.statemachine</groupId>
     <artifactId>spring-statemachine-core</artifactId>
     <version>2.1.2.RELEASE</version>
 </dependency>
           

定義驅動狀态:

public enum OrderStatus {
    WAIT_PAYMENT, // 待付款
    WAIT_DELIVER, // 待發貨
    WAIT_RECEIVE, // 待收貨
    FINISH, // 已收貨
    WAIT_COMMENT, // 待評論
    COMMENTED, // 已評論
    UNCOMMENTED; // 未評論
}
           

定義驅動事件:

public enum ChangeEvent {
    PAYED,  // 支付
    DELIVERY, // 發貨
    RECEIVED, // 收貨
    COMMENT; // 評價
}
           

通過上面定義的狀态和事件實作驅動轉移:

@Configuration
@EnableStateMachine
@Slf4j
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, ChangeEvent> {
    
    @Resource
    private OrderStateListenerImpl orderStateListener;

    @Resource
    private PayedAction payedAction;

    @Resource
    private DeliveryAction deliveryAction;

    @Resource
    private ReceivedAction receivedAction;

    @Resource
    private CommentedAction commentedAction;

    @Resource
    private UncommentedAction uncommentedAction;

    @Resource
    private DeliveryGuard deliveryGuard;

    @Resource
    private PayedGuard payedGuard;

    @Resource
    private ReceivedGuard receivedGuard;

    @Resource
    private CommentGuard commentGuard;


    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, ChangeEvent> states) throws Exception {
        states.withStates()
              // 設定初始化狀态
              .initial(OrderStatus.WAIT_PAYMENT)
              // 設定用于條件判斷的狀态
              .choice(OrderStatus.FINISH)
              // 綁定全部狀态
              .states(EnumSet.allOf(OrderStatus.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, ChangeEvent> transitions) throws Exception {
        // 1、withExternal 是當source和target不同時的寫法
        // 2、withInternal 當source和target相同時的串聯寫法,比如付款失敗時,付款前及付款後都是待付款狀态
        // 3、withChoice 當執行一個動作,可能導緻多種結果時,可以選擇使用choice+guard來跳轉
        transitions
                // 通過PAYED 實作由 WAIT_PAYMENT => WAIT_DELIVER 狀态轉移
                .withExternal()
                    .source(OrderStatus.WAIT_PAYMENT)
                    .target(OrderStatus.WAIT_DELIVER)
                    .event(ChangeEvent.PAYED)
                    .guard(payedGuard)
                    .action(payedAction)
                .and()
                // 通過DELIVERY 實作由 WAIT_DELIVER => WAIT_RECEIVE 狀态轉移
                .withExternal()
                    .source(OrderStatus.WAIT_DELIVER)
                    .target(OrderStatus.WAIT_RECEIVE)
                    .event(ChangeEvent.DELIVERY)
                    .guard(deliveryGuard)
                    .action(deliveryAction)
                .and()
                // 通過RECEIVED 實作由 WAIT_RECEIVE => FINISH 狀态轉移
                .withExternal()
                    .source(OrderStatus.WAIT_RECEIVE)
                    .target(OrderStatus.FINISH)
                    .event(ChangeEvent.RECEIVED)
                    .guard(receivedGuard)
                    .action(receivedAction)
                .and()
                // Choice的狀态選擇,
                // commentGuard的結果為true則執行first
                // commentGuard的結果為true則執行then
                .withChoice()
                    .source(OrderStatus.FINISH)
                    .first(OrderStatus.COMMENTED, commentGuard, commentedAction)
                    .then(OrderStatus.UNCOMMENTED, commentGuard, uncommentedAction)
                    .last(OrderStatus.WAIT_COMMENT);
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStatus, ChangeEvent> config) throws Exception {
        // 狀态轉移監聽事件
        config.withConfiguration().listener(orderStateListener);
    }
}
           

相關方法調用執行前後解釋:

  • withExternal 是當source和target不同時的寫法,比如付款成功後狀态發生的變化。
  • withInternal 當source和target相同時的串聯寫法,比如付款失敗後都是待付款狀态。
  • withExternal的source和target用于執行前後狀态、event為觸發的事件、guard判斷是否執行action。同時滿足source、target、event、guard的條件後執行最後的action。
  • withChoice 當執行一個動作,可能導緻多種結果時,可以選擇使用choice+guard來跳轉
  • withChoice根據guard的判斷結果執行first/then的邏輯。
  • withChoice不需要發送事件來進行觸發。

狀态轉移驅動監聽器:

@Component
@Slf4j
public class OrderStateListenerImpl extends StateMachineListenerAdapter<OrderStatus, ChangeEvent> {
    
    @Override
    public void stateChanged(State<OrderStatus, ChangeEvent> from, State<OrderStatus, ChangeEvent> to) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("stateChanged");
        stringBuilder.append(" from " + (null != from ? from.getId().name() : null));
        stringBuilder.append(" to " + (null != to ? to.getId().name() : null));
        log.info(stringBuilder.toString());
    }

    @Override
    public void transition(Transition<OrderStatus, ChangeEvent> transition) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("transition");
        stringBuilder.append(" kind " + (null != transition.getKind() ? transition.getKind().name() : null));
        stringBuilder.append(" from " + (null != transition.getSource() ? transition.getSource().getId().name() : null));
        stringBuilder.append(" to " + (null != transition.getTarget() ? transition.getTarget().getId().name() : null));
        stringBuilder.append(" trigger " + (null != transition.getTrigger() ? transition.getTrigger().getEvent().name() : null));
        log.info(stringBuilder.toString());
    }
}
           

啟動服務狀态機(初始化所有驅動狀态):

@SpringBootApplication
@Slf4j
public class FsmApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(FsmApplication.class, args);
    }

    @Resource
    private StateMachine<OrderStatus, ChangeEvent> stateMachine;

    @Override
    public void run(String... args) throws Exception {
        stateMachine.start();
        // 測試狀态機消息變更
        messageTransfer();
        stateMachine.stop();
    }

    private void messageTransfer() {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOid(110350339917373440L);
        orderInfo.setDesc("test order");
        Message<ChangeEvent> message = null;
        log.info("current state {}", stateMachine.getState().getId().name());
        // spring message的payload設定為消息事件、header為額外需要帶的參數
        message = MessageBuilder.withPayload(ChangeEvent.PAYED).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        log.info("current state {}", stateMachine.getState().getId().name());
        message = MessageBuilder.withPayload(ChangeEvent.DELIVERY).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        log.info("current state {}", stateMachine.getState().getId().name());
        message = MessageBuilder.withPayload(ChangeEvent.RECEIVED).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        log.info("current state {}", stateMachine.getState().getId().name());
    }
}
           

四、關于StateMachine使用總結

  • 定義狀态機相關狀态和事件驅動枚舉;
  • 狀态機使用的所有狀态以及狀态管理初始化;
  • 驅動狀态轉移事件機制(包含狀态轉移、事件、動作);
  • 為狀态機驅動轉移動作事件實作指定監聽器。