有限狀態機(FSM)java實現
1. 有限狀態機介紹
有限狀態機,也稱爲FSM(Finite State Machine),其在任意時刻都處於有限狀態集合中的某一狀態。當其獲得一個輸入字符時,將從當前狀態轉換到另一個狀態,或者仍然保持在當前狀態。有限狀態機成立的必要條件有:
- 對象有一組互斥的狀態(或對象的生命週期),且這組狀態可以涵蓋對象的創建到消亡的整個過程。
- 當向對象傳入一個信號(或事件)時,對象的狀態能從當前狀態轉換成另一種狀態,或者保持狀態不變。
- 狀態是有限的。
如圖例所示,紅綠燈在同一時間只能亮一個顏色,控制程序可以定義3種不同的事件,每個事件定義好紅綠燈的起始顏色和目標顏色,我們不需要直接去操作紅綠燈開關,只需要按照一定的順序發送事件過去,我們就可以精確控制紅綠燈的工作,紅綠燈的工作控制其實就是一個標準的FSM。
由上所述,FSM一般需要以下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注入方式,事件和狀態的綁定關係可以外掛出來作爲配置項等。