Java架構師方案—透徹理解「狀態機」的全套機制 (附完整項目代碼)


1. 消息傳遞

狀態機可以看成是一個控制中心,接受外部的事件信號進行狀態轉移,而狀態轉移也是在狀態機初始化的時候就設置好了的。但實際業務中,我們不僅僅只是需要控制中心進行狀態轉移,還會需要進行一些業務的處理。

狀態機機制中的事件處理器執行相關業務邏輯,就會需要獲得業務的數據,這時候觸發事件時候就需要傳遞業務數據到處理器中,正好spring的狀態機類statemachine提供了傳遞事件消息的api。如下圖:

alt

/**
     * Send an event {@code E} wrapped with a {@link Message} to the region.
     *
     * @param event the wrapped event to send
     * @return true if event was accepted
     */
    boolean sendEvent(Message<E> event);

    /**
     * Send an event {@code E} to the region.
     *
     * @param event the event to send
     * @return true if event was accepted
     */
    boolean sendEvent(E event);

 

Message實現類

//非完整代碼,因代碼太多,只截取部分展示
public class GenericMessage<T> implements Message<T>, Serializable {

    private static final long serialVersionUID = 4268801052358035098L;


    private final T payload;//這個保存這事件枚舉對象OrderEvents

    private final MessageHeaders headers;//Map類型,保存多個參數對象
......

 

private final T payload;//這個保存這事件枚舉對象OrderEvents

private final MessageHeaders headers;//Map類型,保存多個參數對象

創建的事件消息類Message保存事件對象和參數對象,通過以下方式發送到狀態機。

  //用message傳遞數據
Order order = new Order(orderId, "547568678", "廣東省深圳市", "13435465465", "RECEIVE");
Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).setHeader("otherObj", "otherObjValue").build();
stateMachine.sendEvent(message);

 

RECEIVE事件被觸發,事件處理器被觸發打印如下結果:

消息: Order [id=null, userId=547568678, address=廣東省深圳市, phoneNum=13435465465, state=RECEIVE]

 

RECEIVE事件對應的處理器方法:

    /**
     * WAITING_FOR_RECEIVE->DONE 執行的動作
     */
    @OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
    public void receive(Message<OrderEvents> message) {
        logger.info("---【WithStateMachine】用戶已收貨,訂單完成---");
        Object object = message.getHeaders().get("order");
        Order order = null;
        if(!ObjectUtils.isEmpty(object))
        {
            order = (Order)object;
            System.out.println("消息: "+order.toString());
        }else {
            System.out.println("消息為空");
        }
    }

 

處理器類使用@WithStateMachine(name="orderSingleMachine")修飾,name屬性關聯着狀態機。事件處理器方法使用@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")修飾,表明現態和次態。

小結:這樣就實現了狀態機業務層controller到狀態機處理器的業務數據傳遞,以消息的形式傳遞。

2. 事件監聽

狀態機創建,使用@EnableStateMachine注解修飾繼承EnumStateMachineConfigurerAdapter適配類的子類。而狀態機的監聽器就是在這個子類中注入的。

    //一定要注意這里設置監聽器,那么該監聽器就只能作用於 此狀態機,狀態機工廠 創建的狀態機無法創建
    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        // TODO Auto-generated method stub
        config.withConfiguration().listener(listener());
    }

 

創建監聽器創建對象

default  StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<OrderStates, OrderEvents>() {

            @Override
            public void transition(Transition<OrderStates, OrderEvents> transition) {
                if(transition.getTarget().getId() == OrderStates.UNPAID) {
                    logger.info("【StateMachineListener】訂單創建,待支付");
                    return;
                }

                if(transition.getSource().getId() == OrderStates.UNPAID
                        && transition.getTarget().getId() == OrderStates.WAITING_FOR_RECEIVE) {
                    logger.info("【StateMachineListener】用戶完成支付,待收貨");
                    return;
                }

                if(transition.getSource().getId() == OrderStates.WAITING_FOR_RECEIVE
                        && transition.getTarget().getId() == OrderStates.DONE) {
                    logger.info("【StateMachineListener】用戶已收貨,訂單完成");
                }

                
                if(transition.getSource().getId() == OrderStates.WAITING_FOR_RECEIVE
                        && transition.getTarget().getId() == OrderStates.WAITING_FOR_GOODSBACK) {
                    logger.info("【StateMachineListener】用戶已退貨,待商家收貨");
                }
                
                if(transition.getSource().getId() == OrderStates.WAITING_FOR_GOODSBACK
                        && transition.getTarget().getId() == OrderStates.DONE) {
                    logger.info("【StateMachineListener】商家已退款,訂單完成");
                }
                
                if(transition.getTarget().getId() == OrderStates.DONE) {
                    logger.info("【StateMachineListener】訂單結束");
                }
            }

        };
    }
    
}

 

注意:這種監聽器配置后,作用的只能是這個@EnableStateMachine注解創建的狀態機配置類創建的狀態機。跟spring的狀態機工廠配置監聽器是同樣的方式,不過修飾的注解是@EnableStateMachineFactory,具體實現請看本項目源碼,這里不再贅述。

監聽器效果:

最終狀態:DONE
otherOrderStateFactoryMachine
【OrderFactoryOtherEventConfig】---訂單創建,待支付---
【StateMachineListener】訂單創建,待支付
 started 
【OrderFactoryOtherEventConfig】---用戶完成支付,待收貨---
【StateMachineListener】用戶完成支付,待收貨
【OrderFactoryOtherEventConfig】---用戶已收貨,訂單完成---
【StateMachineListener】用戶已收貨,訂單完成
【StateMachineListener】訂單結束
最終狀態:DONE

 

從結果可知:

  • 監聽器總是比事件處理器后執行。
  • 監聽器只能監聽狀態變更,無法獲取事件參數,處理器可以

3. 多個以及多種狀態機並存

  1. 在項目中@EnableStateMachine(name="orderSingleMachine")以spring提供的方式創建了一個狀態機對象

  2. StateMachineConfig類中配置了狀態枚舉數據與初態,狀態轉移圖譜與事件,監聽器

  3. 在OrderSingleEventConfig類里配置了處理器的邏輯

  4. 啟動項目第一次調用 http://localhost/statemachine/testSingleOrderState,結果如下:

---【WithStateMachine】訂單創建,待支付---
【StateMachineListener】訂單創建,待支付
 ---【WithStateMachine】用戶完成支付,待收貨---
【StateMachineListener】用戶完成支付,待收貨
---【WithStateMachine】用戶已收貨,訂單完成---
消息為空
【StateMachineListener】用戶已收貨,訂單完成
【StateMachineListener】訂單結束
最終狀態:DONE

 

多次調用顯示如下:

最終狀態:DONE
最終狀態:DONE
最終狀態:DONE
最終狀態:DONE
最終狀態:DONE
最終狀態:DONE

 

因此:這種方式創建的狀態機只能用一次。

這時候就需要用到狀態機工廠來多次創建狀態機。

好在工廠方式跟上面方式一樣,只有注解不同。工廠方式使用@EnableStateMachineFactory。使用工廠方式可以多次創建狀態機對象,這樣就解決了上面一直是最終狀態的問題。工廠方式和普通方式的區別:

  • 使用的注解不一樣,工廠方式:@EnableStateMachineFactory,普通方式:@EnableStateMachine。

  • 工廠方式創建的狀態機name需要在創建的時候指定,普通方式的name就是注解的name屬性,如下代碼:

orderMachineFactory.getStateMachine(StateMachineFactoryConfig.orderStateMachineId);

 

4. 狀態分支

前面提到的都是簡單的狀態轉移,有些時候我們需要做一些條件判斷,根據條件結果再做狀態轉移。而這種條件判斷的狀態都是短暫停留等待條件結果,然后再跳轉到下一個狀態。

這跟前面提到的一個狀態跳轉到兩個次態對應兩個事件情況是不一樣的,這里的情況是分支場景,而statemachine用withChoice方法實現了分支,具體實現可以看項目源碼的復雜表單狀態機實現。

配置狀態圖譜代碼:

.withChoice()
.source(ComplexFormStates.CHECK_CHOICE)
.first(ComplexFormStates.CONFIRM_FORM, new ComplexFormCheckChoiceGuard(),new ComplexFormChoiceAction())
.last(ComplexFormStates.DEAL_FORM,new ComplexFormChoiceAction())

 

first方法表示條件判斷為true是的狀態轉移,last方法表示條件為false時的狀態轉移。

ComplexFormCheckChoiceGuard類是條件判斷類,ComplexFormChoiceAction類是狀態轉移到CONFIRM_FORM后執行的動作。

具體測試樣例請查看我的項目源碼。

5. 狀態機工廠

狀態機工廠創建狀態機在本項目中有兩種方式:

  • 一種是前面介紹的使用@EnableStateMachineFactory注解

  • 使用spring的StateMachineBuilder狀態機構造器,用工廠類包裝,具體實現是FormStateMachineBuilder和ComplexFormStateMachineBuilder。

 

查看更多 “Java架構師方案” 系列文章 以及 SpringBoot2.0學習示例

 

 

 

 


如果大家覺得這篇文章對你學習架構有幫助的話,還請點贊,在看支持一下。github項目也記得點個星哦!

 

想要更多狀態機機制實現詳情,請關注公眾號“前沿科技bot“,發送"狀態機2"獲取完整項目源碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM