狀態機是“有限狀態自動機”的簡稱,是一種描述和處理事物狀態變化的數學模型。本質上來講,就是一種比if...else
結構更加優雅,並具備可擴展性的狀態轉移處理機制。有多種實現方案,如:枚舉,Spring Statemachine,cola state machine。
枚舉狀態機
通過在枚舉中定義方法來實現狀態轉移,狀態定義及轉換圖示例如下。
public enum FlowState {
SUBMIT_APPLY {
FlowState transition(String condition) {
System.out.println(String.format("員工提交申請,同步流轉到部門經理申請,參數:%s", condition));
return DEPARTMENT_MANAGER_AUDIT;
}
},
DEPARTMENT_MANAGER_AUDIT {
FlowState transition(String condition) {
System.out.println(String.format("部門經理審批完成,同步流轉到HR審批,參數:%s", condition));
return HR;
}
},
HR {
FlowState transition(String condition) {
System.out.println(String.format("HR審批通過,流轉到結束組件,參數:%s", condition));
return END;
}
},
END {
FlowState transition(String condition) {
System.out.println(String.format("流程結束,參數:%s", condition));
return this;
}
};
// 在枚舉中可以定義抽象方法,然後在具體的枚舉實例中實現該方法
abstract FlowState transition(String condition);
}
枚舉狀態機使用示例:
public class EnumStateMachineSample {
private FlowState flowState;
public EnumStateMachineSample() {
this.flowState = FlowState.SUBMIT_APPLY;
}
public void perform(String condition) {
flowState = flowState.transition(condition);
}
public static void main(String[] args) {
EnumStateMachineSample sample = new EnumStateMachineSample();
sample.perform("arg1");
sample.perform("arg2");
sample.perform("arg3");
sample.perform("arg4");
}
}
輸出:
員工提交申請,同步流轉到部門經理申請,參數:arg1
部門經理審批完成,同步流轉到HR審批,參數:arg2
HR審批通過,流轉到結束組件,參數:arg3
流程結束,參數:arg4
如上,在運行枚舉狀態機之後,各個狀態的轉換按預定方式執行。
Spring Statemachine
依賴Spring框架,具備完整的狀態機功能設計。
狀態及轉換圖設計如下:
添加依賴:
<!-- spring-statemachine-core -->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.0.1</version>
</dependency>
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
通過註解方式配置狀態機:
@Configuration
@EnableStateMachine
public class StateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(StateMachineConfiguration.class);
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
// 定義初始節點,結束節點,狀態節點
states.withStates()
.initial("SS") // 初始節點
.end("SF") // 結束節點
.states(new HashSet<String>(Arrays.asList("S1", "S2"))); // 狀態節點
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
// 配置狀態節點的事件,流向以及動作
transitions.withExternal() // 外部流轉
.source("SS").target("S1").event("E1").action(initAction())
.and()
.withExternal()
.source("S1").target("S2").event("E2").action(s1Action())
.and()
.withExternal()
.source("S2").target("SF").event("E3").action(s2Action());
}
@Bean
public Action<String, String> initAction() {
return new Action<String, String>() {
public void execute(StateContext<String, String> context) {
LOGGER.info("Init MyAction -- target: {}", context.getTarget().getId());
}
};
}
@Bean
public Action<String, String> s1Action() {
return new Action<String, String>() {
public void execute(StateContext<String, String> context) {
LOGGER.info("S1 MyAction -- target: {}", context.getTarget().getId());
}
};
}
private Action<String, String> s2Action() {
return new Action<String, String>() {
@Override
public void execute(StateContext<String, String> context) {
LOGGER.info("S2 MyAction -- target: {}", context.getTarget().getId());
}
};
}
// 通過Bean註解裝配其他bean的屬性
@Bean
public StateMachineContext stateMachineContext(
@Qualifier("stateMachine") StateMachine stateMachine,
@Qualifier("stateMachineListener") StateMachineListener stateMachineListener) {
// 設置狀態機監聽器
stateMachine.addStateListener(stateMachineListener);
return new StateMachineContext(stateMachine);
}
}
// StateMachineContext.java
// 這個類的定義不是必須的,存粹是爲了方便通過@Bean註解方式配置狀態機監聽器StateMachineListener
public class StateMachineContext {
private StateMachine stateMachine;
public StateMachineContext(StateMachine stateMachine) {
this.stateMachine = stateMachine;
}
public StateMachine getStateMachine() {
return stateMachine;
}
public void setStateMachine(StateMachine stateMachine) {
this.stateMachine = stateMachine;
}
}
// 狀態機監聽器
@Component
public class StateMachineListener extends StateMachineListenerAdapter<String, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(StateMachineListener.class);
@Override
public void stateChanged(State<String, String> from, State<String, String> to) {
LOGGER.info("Transitioned from {} to {}", from == null ? "none" : from.getId(), to.getId());
}
}
Spring StateMachine使用示例:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("org.chench.extra.java.statemachine.spring"); // 設置狀態機相關配置及組件所在的包名
context.refresh();
// 從Spring容器中獲取狀態機實例
StateMachine<String, String> stateMachine = context.getBean(StateMachine.class);
// 開啓狀態機
stateMachine.startReactively().block();
// 事件操作,觸發狀態機內部的狀態轉換
Message<String> messageE1 = MessageBuilder.withPayload("E1").build();
stateMachine.sendEvent(Mono.just(messageE1)).blockLast();
Message<String> messageE2 = MessageBuilder.withPayload("E2").build();
stateMachine.sendEvent(Mono.just(messageE2)).blockLast();
Message<String> messageE3 = MessageBuilder.withPayload("E3").build();
stateMachine.sendEvent(Mono.just(messageE3)).blockLast();
// 停止狀態機
stateMachine.stopReactively().block();
}
}
輸出信息如下:
Transitioned from none to SS -- 狀態機進入初始狀態
Init MyAction -- target: S1 -- 狀態機從SS狀態轉換到S1狀態執行的動作
Transitioned from SS to S1 -- 狀態機從SS狀態轉換到S1狀態
S1 MyAction -- target: S2 -- 狀態機從S1狀態轉換到S2狀態執行的動作
Transitioned from S1 to S2 -- 狀態機從S1狀態轉換到S2狀態
S2 MyAction -- target: SF -- 狀態機從S2狀態轉換到SF狀態執行的動作
Transitioned from S2 to SF -- 狀態機從S2狀態轉換到SF狀態
Cola狀態機
Cola狀態機的使用和配置比Spring StateMachine簡單直接,以電商場景的訂單狀態轉換爲例進行闡述。
設計訂單狀態及其轉化圖如下:
添加依賴:
<!-- cola-component-statemachine -->
<dependency>
<groupId>com.alibaba.cola</groupId>
<artifactId>cola-component-statemachine</artifactId>
<version>4.3.2</version>
</dependency>
分別定義訂單狀態,訂單事件,訂單上下文。
// 訂單狀態
public enum OrderStatus {
INIT("初始化", 0),
PAY_ONLINE("待支付", 1),
WAITING_FOR_RECEIVED("待接單", 2),
WAITING_DELIVERY("待發貨", 3),
PART_DELIVERY("部分發貨", 4),
ALL_DELIVERY("全部發貨", 5),
DELIVER_ALL("待收貨", 6),
RECEIVED("已收貨", 7),
DONE("已完成", 8),
CANCEL("已關閉", 9);
private String name;
private int code;
OrderStatus(String name, int code) {
this.name = name;
this.code = code;
}
public String getName() {
return name;
}
public int getCode() {
return code;
}
}
// 訂單事件
public enum OrderEvent {
CREATE_ORDER(1, "創建訂單"),
REPAY(2, "支付"),
CANCEL_ORDER(3, "取消訂單"),
TAKE_ORDER(4, "接單"),
REJECT_ORDER(5, "拒單"),
DELIVERY_PART(6, "部分發貨"),
DELIVERY_ALL(7, "全部發貨"),
CONFIRM_RECEIPT(8, "確認收貨"),
EXTEND_RECEIPT(9, "延長收貨"),
COMPLETE(10, "交易完成");
private int code;
private String name;
OrderEvent(int code, String name) {
this.code = code;
this.name = name;
}
public int getCode() {
return code;
}
public String getName() {
return name;
}
}
// 訂單上下文
public class OrderContext {
private String orderId;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
使用Cola狀態機:
public class ColaStateMachineSample {
// 狀態機實例
static StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine;
public static void main(String[] args) {
// 狀態機構建器
StateMachineBuilder<OrderStatus, OrderEvent, OrderContext> builder = StateMachineBuilderFactory.create();
// 設置訂單狀態轉換路徑
// 初始化 -> 待支付
builder.externalTransition()
.from(OrderStatus.INIT)
.to(OrderStatus.PAY_ONLINE)
.on(OrderEvent.CREATE_ORDER)
.perform(createOrderAction());
// 待支付 -> 待接單
builder.externalTransition()
.from(OrderStatus.PAY_ONLINE)
.to(OrderStatus.WAITING_FOR_RECEIVED)
.on(OrderEvent.REPAY)
.perform(waitingOrderAction());
// 待接單 -> 待發貨
builder.externalTransition()
.from(OrderStatus.WAITING_FOR_RECEIVED)
.to(OrderStatus.WAITING_DELIVERY)
.on(OrderEvent.TAKE_ORDER)
.perform(takeOrderAction());
// 待發貨 -> 部分發貨
builder.externalTransition()
.from(OrderStatus.WAITING_DELIVERY)
.to(OrderStatus.PART_DELIVERY)
.on(OrderEvent.DELIVERY_PART)
.perform(deliverPartOrderAction());
// 部分發貨 -> 部分發貨 :內部轉換
builder.internalTransition()
.within(OrderStatus.PART_DELIVERY)
.on(OrderEvent.DELIVERY_PART)
.when(checkOrder())
.perform(deliverPartOrderAction());
// 部分發貨 -> 全部發貨
builder.externalTransition()
.from(OrderStatus.PART_DELIVERY)
.to(OrderStatus.ALL_DELIVERY)
.on(OrderEvent.DELIVERY_ALL)
.perform(deliverAllOrderAction());
// 待發貨 -> 全部發貨
builder.externalTransition()
.from(OrderStatus.WAITING_DELIVERY)
.to(OrderStatus.ALL_DELIVERY)
.on(OrderEvent.DELIVERY_ALL)
.perform(deliverAllOrderAction());
// 全部發貨 -> 待收貨
builder.externalTransition()
.from(OrderStatus.ALL_DELIVERY)
.to(OrderStatus.DELIVER_ALL)
.on(OrderEvent.DELIVERY_ALL)
.perform(receiveConfirmOrderAction());
// 待收貨 -> 已收貨
builder.externalTransition()
.from(OrderStatus.DELIVER_ALL)
.to(OrderStatus.RECEIVED)
.on(OrderEvent.CONFIRM_RECEIPT)
.perform(receivedOrderAction());
// 待支付,待接單,待發貨,待收貨 -> 已關閉
builder.externalTransitions()
.fromAmong(OrderStatus.PAY_ONLINE, OrderStatus.WAITING_FOR_RECEIVED, OrderStatus.WAITING_DELIVERY, OrderStatus.DELIVER_ALL)
.to(OrderStatus.CANCEL)
.on(OrderEvent.CANCEL_ORDER)
.perform(cancelOrderAction());
// 已收貨 -> 已完成
builder.externalTransition()
.from(OrderStatus.RECEIVED)
.to(OrderStatus.DONE)
.on(OrderEvent.COMPLETE)
.perform(doneOrderAction());
// 構建狀態機實例
stateMachine = builder.build("ColaStateMachineSample");
// 觸發創建訂單事件,狀態轉換:“初始化” -> “待支付”
stateMachine.fireEvent(OrderStatus.INIT, OrderEvent.CREATE_ORDER, new OrderContext());
}
// 創建訂單
private static Action<OrderStatus, OrderEvent, OrderContext> createOrderAction() {
return (from, to, event, context) -> {
System.out.println("創建訂單");
// 1.獲取狀態機
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
// 2.組裝訂單上下文
OrderContext orderContext = getOrderContext();
// 3.觸發狀態機事件
stateMachine.fireEvent(OrderStatus.PAY_ONLINE, OrderEvent.REPAY, orderContext);
};
}
// 待接單
private static Action<OrderStatus, OrderEvent, OrderContext> waitingOrderAction() {
return (from, to, event, context) -> {
System.out.println("待接單");
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
stateMachine.fireEvent(OrderStatus.WAITING_FOR_RECEIVED, OrderEvent.TAKE_ORDER, getOrderContext());
};
}
// 待發貨
private static Action<OrderStatus, OrderEvent, OrderContext> takeOrderAction() {
return (from, to, event, context) -> {
System.out.println("待發貨");
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
// 檢查是否全部發貨
int r = new Random().nextInt(10);
if (r % 3 == 0) {
// 全部發貨
stateMachine.fireEvent(OrderStatus.WAITING_DELIVERY, OrderEvent.DELIVERY_ALL, getOrderContext());
} else {
// 部分發貨
stateMachine.fireEvent(OrderStatus.WAITING_DELIVERY, OrderEvent.DELIVERY_PART, getOrderContext());
}
};
}
// 部分發貨
private static Action<OrderStatus, OrderEvent, OrderContext> deliverPartOrderAction() {
return (from, to, event, context) -> {
System.out.println("部分發貨");
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
// 檢查是否還是部分發貨
int r = new Random().nextInt(10);
if (r % 2 == 0) {
// 還是部分發貨
stateMachine.fireEvent(OrderStatus.PART_DELIVERY, OrderEvent.DELIVERY_PART, getOrderContext());
} else {
// 全部發貨
stateMachine.fireEvent(OrderStatus.PART_DELIVERY, OrderEvent.DELIVERY_ALL, getOrderContext());
}
};
}
// 檢查訂單狀態
private static Condition<OrderContext> checkOrder() {
return new Condition<OrderContext>() {
@Override
public boolean isSatisfied(OrderContext context) {
return true;
}
};
}
// 全部發貨
private static Action<OrderStatus, OrderEvent, OrderContext> deliverAllOrderAction() {
return ((from, to, event, context) -> {
System.out.println("全部發貨");
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
stateMachine.fireEvent(OrderStatus.ALL_DELIVERY, OrderEvent.DELIVERY_ALL, getOrderContext());
});
}
// 待收貨
private static Action<OrderStatus, OrderEvent, OrderContext> receiveConfirmOrderAction() {
return ((from, to, event, context) -> {
System.out.println("待收貨");
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
stateMachine.fireEvent(OrderStatus.DELIVER_ALL, OrderEvent.CONFIRM_RECEIPT, getOrderContext());
});
}
// 已收貨
private static Action<OrderStatus, OrderEvent, OrderContext> receivedOrderAction() {
return ((from, to, event, context) -> {
System.out.println("已收貨");
StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine = getStateMachine();
stateMachine.fireEvent(OrderStatus.RECEIVED, OrderEvent.COMPLETE, getOrderContext());
});
}
// 已關閉
private static Action<OrderStatus, OrderEvent, OrderContext> cancelOrderAction() {
return ((from, to, event, context) -> {
System.out.println("已關閉");
});
}
// 已完成
private static Action<OrderStatus, OrderEvent, OrderContext> doneOrderAction() {
return ((from, to, event, context) -> {
System.out.println("已完成");
});
}
private static StateMachine<OrderStatus, OrderEvent, OrderContext> getStateMachine() {
return stateMachine;
}
private static OrderContext getOrderContext() {
return new OrderContext();
}
}
輸出:
創建訂單
待接單
待發貨
部分發貨
全部發貨
待收貨
已收貨
已完成
分佈式事務框架Seata的SAGA模式就是使用狀態機機制實現的,但是其狀態轉換並非硬編碼配置,而是通過狀態機設計器進行編排最終導出爲json格式文件,每一個分佈式事務業務流程就對應一個編排文件。
【參考】
什麼是狀態機?一篇文章就夠了
狀態機的介紹和使用
狀態機的技術選型看這篇就夠了,最後一個直叫好!!!
全網首發:Seata Saga狀態機設計器實戰
如何將Saga建模爲狀態機
保姆式教程!如何使用Cola-statemachine構建高可靠性的狀態機 ?
Cola-StateMachine狀態機的實戰使用