有限狀態機(FSM)java實現

有限狀態機(FSM)java實現

1. 有限狀態機介紹

有限狀態機,也稱爲FSM(Finite State Machine),其在任意時刻都處於有限狀態集合中的某一狀態。當其獲得一個輸入字符時,將從當前狀態轉換到另一個狀態,或者仍然保持在當前狀態。有限狀態機成立的必要條件有:

  1. 對象有一組互斥的狀態(或對象的生命週期),且這組狀態可以涵蓋對象的創建到消亡的整個過程。
  2. 當向對象傳入一個信號(或事件)時,對象的狀態能從當前狀態轉換成另一種狀態,或者保持狀態不變。
  3. 狀態是有限的。

如圖例所示,紅綠燈在同一時間只能亮一個顏色,控制程序可以定義3種不同的事件,每個事件定義好紅綠燈的起始顏色和目標顏色,我們不需要直接去操作紅綠燈開關,只需要按照一定的順序發送事件過去,我們就可以精確控制紅綠燈的工作,紅綠燈的工作控制其實就是一個標準的FSM。

由上所述,FSM一般需要以下4個部分組成:

  1. 對象狀態枚舉。
  2. 對象事件枚舉,並指定事件的起始狀態和目標狀態。
  3. 事件邏輯體,用於處理狀態變更引起的業務邏輯。
  4. 事件註冊工廠類,FSM的唯一入口。

2. 從需求開始分析FSM

當我們拿到一個類似需求時應該怎麼入手呢?下面我們以一個簡單的商城訂單的FSM實現爲例,從需求分析到代碼實現爲大家講解:

首先我們應該從需求中提煉出一個訂單具體需要經過哪些狀態(這裏我只列舉了一個簡單訂單的正向狀態)。

訂單狀態:待支付、待發貨、待收貨、已取消、已完成

列舉出所有狀態之後,在作圖工具中把狀態全部畫出來,每個狀態進行分析是否能轉換爲其他狀態,如分析待支付狀態,用戶可以從待支付狀態進行付款事件,待支付狀態將會轉換爲待發貨,用戶也可以從待支付狀態取消支付,待支付將會轉換爲訂單取消狀態。按照這個思路使用單向箭頭將所有事件列舉出來,並給每個事件起名字。

訂單事件:下單事件、支付事件、發貨事件、支付取消事件、收貨事件

最後形成如下圖所示的狀態流轉圖

3. FSM的java實現

按照上面所說,我們將FSM的4個部分聲明出來:

3.1. 對象狀態枚舉類

使用枚舉的方式窮舉出訂單所有可能的狀態。

public enum OrderStatusEnum {
    Unpaid("Unpaid", "待支付"),
    UnShipping("UnShipping", "待發貨"),
    UnReceiving("UnReceiving", "待收貨"),

    Canceled("Canceled", "已取消"),
    Finished("Finished", "已完成");

    private final String code;
    private final String desc;

    OrderStatusEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

3.2. 對象事件枚舉類

爲了省事,我在這裏把事件和狀態做了關聯,也可以單獨拿出來做一個配置封裝。

public enum OrderEventEnum {
    CreateOrder("CreateOrder", "下單事件", null, OrderStatusEnum.Unpaid),
    Payment("Payment", "支付事件", OrderStatusEnum.Unpaid, OrderStatusEnum.UnShipping),
    Shipping("Shipping", "發貨事件", OrderStatusEnum.UnShipping, OrderStatusEnum.UnReceiving),
    Receiving("Receiving", "收貨事件", OrderStatusEnum.UnReceiving, OrderStatusEnum.Finished),
    CancelPayment("CancelPayment", "支付取消事件", OrderStatusEnum.Unpaid, OrderStatusEnum.Canceled);

    private final String code;
    private final String desc;
    private final OrderStatusEnum sourceOrderStatus;
    private final OrderStatusEnum targetOrderStatus;

    OrderEventEnum(String code, String desc, OrderStatusEnum sourceOrderStatus, OrderStatusEnum targetOrderStatus) {
        this.code = code;
        this.desc = desc;
        this.sourceOrderStatus = sourceOrderStatus;
        this.targetOrderStatus = targetOrderStatus;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    public OrderStatusEnum getSourceOrderStatus() {
        return sourceOrderStatus;
    }

    public OrderStatusEnum getTargetOrderStatus() {
        return targetOrderStatus;
    }
}

3.3. 事件邏輯體

首先聲明一個邏輯體基類,基類裏面定義了事件過程數據緩存對象、事件條件校驗方法、事件後處理方法、事件業務邏輯處理虛方法(需要由業務自己實現)。

@Slf4j
public abstract class BaseOrderFsmProcessor {
    private static final Map<Long, Object> FSM_DATA_MAP = new ConcurrentHashMap<>();

    /**
     * 執行業務邏輯
     *
     * @param orderId 訂單ID
     * @param event   事件類型
     * @return 成功or失敗
     */
    public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
        log.info("OrderFSM_開始FSM事件:orderId={}", orderId);
        if (!checkRule(orderId, event)) {
            log.warn("OrderFSM_不滿足條件,拒絕執行FSM事件:orderId={}", orderId);
            return true;
        }
        boolean b = process(orderId, event);
        log.info("OrderFSM_結束FSM事件:orderId={}", orderId);
        postHandler(orderId, event);
        return b;
    }

    /**
     * 業務邏輯實現類
     *
     * @param orderId 訂單ID
     * @param event   事件類型
     * @return 成功or失敗
     * @throws Exception ex
     */
    public abstract boolean process(Long orderId, OrderEventEnum event) throws Exception;

    /**
     * 校驗是否滿足條件執行當親process
     *
     * @param orderId 訂單ID
     * @param event   事件類型
     * @return 成功or失敗
     */
    public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
        return true;
    }

    /**
     * 後置處理邏輯
     *
     * @param orderId 訂單ID
     * @param event   事件類型
     */
    public void postHandler(Long orderId, OrderEventEnum event) throws Exception {

    }

    /**
     * 根據流程ID獲取事件過程數據
     *
     * @param orderId 訂單ID
     * @return 事件過程數據
     */
    public static Object getFsmData(Long orderId) {
        return FSM_DATA_MAP.remove(orderId);
    }

    /**
     * 根據流程ID設置事件過程數據
     *
     * @param orderId 訂單ID
     * @param obj     事件過程數據
     */
    public static void setFsmData(Long orderId, Object obj) {
        FSM_DATA_MAP.put(orderId, obj);
    }

}

再跟進基類派生出不同事件對應的處理方法,一個事件要聲明一個對應的方法:

@Slf4j
public class CreateOrderProcessor extends BaseOrderFsmProcessor {

    @Override
    public boolean process(Long orderId, OrderEventEnum event) throws Exception {
        log.info("業務邏輯執行中:className={},event={}", getClass().getSimpleName(), event.name());
        //TODO 模擬業務邏輯
        TimeUnit.MILLISECONDS.sleep(1000);
        setFsmData(orderId, Thread.currentThread().getName());

        log.info("業務邏輯執行完成");
        return true;
    }

    @Override
    public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
        log.info("執行條件檢查通過");
        return true;
    }

    @Override
    public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
        log.info("執行後置處理邏輯");
        // TODO 將orderId的狀態改爲 event.getTargetOrderStatus()
    }
}

3.4. 事件註冊工廠類

工廠類裏封裝了單例FSM執行的唯一入口和查詢設置流程中間數據的入口。

@Slf4j
public class OrderFsmManager {

    private final Map<OrderEventEnum, BaseOrderFsmProcessor> orderProcessorMap = new HashMap<>();
    private volatile static OrderFsmManager orderFsmManager;

    private OrderFsmManager() {
        orderProcessorMap.put(OrderEventEnum.CreateOrder, new CreateOrderProcessor());
        orderProcessorMap.put(OrderEventEnum.Payment, new PaymentFsmProcessor());
    }

    /**
     * 獲取fsm實例
     */
    public static OrderFsmManager getInstance() {
        if (orderFsmManager == null) {
            synchronized (OrderFsmManager.class) {
                if (orderFsmManager == null) {
                    orderFsmManager = new OrderFsmManager();
                }
            }
        }
        return orderFsmManager;
    }

    /**
     * 開始執行fsm事件
     *
     * @param orderId 訂單ID
     * @param event   事件類型
     * @return 成功or失敗
     */
    public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
        if (!orderProcessorMap.containsKey(event)) {
            throw new Exception(String.format("MediaProcessFSM沒有匹配到事件:orderId=%s,currentOrderEvent=%s"
                    , orderId, event));
        }
        return orderProcessorMap.get(event).fireProcess(orderId, event);
    }

    /**
     * 根據流程ID獲取事件過程數據
     *
     * @param orderId 訂單ID
     * @return 事件過程數據
     */
    public Object getFsmData(Long orderId) {
        return BaseOrderFsmProcessor.getFsmData(orderId);
    }

    /**
     * 根據流程ID設置事件過程數據
     *
     * @param orderId 訂單ID
     * @param obj     事件過程數據
     */
    public void setFsmData(Long orderId, Object obj) {
        BaseOrderFsmProcessor.setFsmData(orderId, obj);
    }
}

3.5. 簡單做個測試

	public static void main(String[] args) throws Exception {
        OrderFsmManager orderFsmManager = OrderFsmManager.getInstance();
//        boolean b1 = orderFsmManager.fireProcess(1L, OrderEventEnum.CreateOrder);
//        boolean b2 = orderFsmManager.fireProcess(2L, OrderEventEnum.Payment);
//        System.out.println(String.format("orderId=%s,data=%s",1, orderFsmManager.getFsmData(1L)));
//        System.out.println(String.format("orderId=%s,data=%s",2, orderFsmManager.getFsmData(2L)));

        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                String threadName = "thread-" + finalI;
                Thread.currentThread().setName(threadName);

                try {
                    if (finalI%2==0){
                        boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.CreateOrder);
                    }else {
                        boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.Payment);
                    }

                    System.out.println(String.format("threadName=%s,data=%s",threadName, orderFsmManager.getFsmData((long) finalI)));
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }).start();
        }


    }
11:17:39.213 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_開始FSM事件:orderId=2
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 執行條件檢查通過
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 業務邏輯執行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.213 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_開始FSM事件:orderId=1
11:17:39.213 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_開始FSM事件:orderId=0
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 執行條件檢查通過
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 執行條件檢查通過
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 業務邏輯執行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 業務邏輯執行中:className=PaymentFsmProcessor,event=Payment
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 業務邏輯執行完成
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_結束FSM事件:orderId=1
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 執行後置處理邏輯
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 業務邏輯執行完成
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_結束FSM事件:orderId=2
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 執行後置處理邏輯
threadName=thread-1,data=thread-1
threadName=thread-2,data=thread-2
11:17:40.246 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 業務邏輯執行完成
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_結束FSM事件:orderId=0
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 執行後置處理邏輯
threadName=thread-0,data=thread-0

4. 結語

到這裏FSM的封裝就完成了,其中有一些設計可以根據自身技術選擇進行調整,如工廠類的單例實現可以使用spring的bean注入方式,事件和狀態的綁定關係可以外掛出來作爲配置項等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章