目錄
一. 背景
二. 概念
1.1 狀態(tài)機(jī)模型的概念
2.2 組成要素
3.3 三個(gè)特征
4.4 執(zhí)行邏輯
5.5 分類
6.6 表示法
三. 狀態(tài)機(jī)在軟件領(lǐng)域的應(yīng)用
3.1 應(yīng)用場景
3.2 編碼中如何運(yùn)用狀態(tài)機(jī)
四. Spring State Machine 介紹
4.1 項(xiàng)目概要
4.2 使用場景
4.3 要素和基本概念
4.4 例子
4.5 基本原理
4.6 使用狀態(tài)機(jī)基本原則
4.7 值得思考
五. 狀態(tài)機(jī)是一種思維方式
一 背景
有限狀態(tài)機(jī)FSM(Finite State Machine),相信有些讀者聽說過,或者使用過。但是了解的人似乎并不多。
在硬件領(lǐng)域,狀態(tài)機(jī)是由狀態(tài)寄存器和組合邏輯電路構(gòu)成,能夠根據(jù)控制信號按照預(yù)先設(shè)定的狀態(tài)進(jìn)行狀態(tài)轉(zhuǎn)移,是協(xié)調(diào)相關(guān)信號動(dòng)作、完成特定操作的控制中心。
狀態(tài)機(jī)的概念其實(shí)已經(jīng)很老了,有限自動(dòng)機(jī)的描述可以追溯到1943年,當(dāng)時(shí) Warren McCulloch 和 Walter Pitts 先生寫了一篇關(guān)于它的論文。后來,George H.Mealy 在1955年提出了一個(gè)狀態(tài)機(jī)概念,稱為Mealy機(jī)。一年后的1956年,Edward F.Moore 提出了另一篇被稱為Moore機(jī)的論文。后來這個(gè)概念被廣泛應(yīng)用于語言學(xué)、計(jì)算機(jī)科學(xué)、生物學(xué)、數(shù)學(xué)和邏輯學(xué),甚至于哲學(xué)等各種領(lǐng)域。
在計(jì)算機(jī)科學(xué)中,有限狀態(tài)機(jī)被廣泛用于應(yīng)用行為建模、硬件電路系統(tǒng)設(shè)計(jì)、軟件工程,編譯器、網(wǎng)絡(luò)協(xié)議、和計(jì)算與語言的研究。
今天我們來聊聊狀態(tài)機(jī)思維,以及它在計(jì)算機(jī)軟件開發(fā)領(lǐng)域中的應(yīng)用。
二 概念
2.1 狀態(tài)機(jī)模型的概念
有限狀態(tài)機(jī)(英語:finite-state machine,縮寫:FSM)又稱有限狀態(tài)自動(dòng)機(jī),簡稱狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型。

2.2 組成要素
現(xiàn)態(tài):是指當(dāng)前所處的狀態(tài)。
條件:又稱為
事件。當(dāng)一個(gè)條件被滿足,可能將會觸發(fā)一個(gè)動(dòng)作,或者執(zhí)行一次狀態(tài)的遷移。動(dòng)作:條件滿足后執(zhí)行的
動(dòng)作行為。動(dòng)作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。動(dòng)作不是必需的,當(dāng)條件滿足后,也可以不執(zhí)行任何動(dòng)作,直接遷移到新狀態(tài)。*次態(tài):條件滿足后要遷往的新狀態(tài)?!按螒B(tài)”是相對于“現(xiàn)態(tài)”而言的,“次態(tài)”一旦被激活,就轉(zhuǎn)變成新的“現(xiàn)態(tài)”了。
2.3 三個(gè)特征
狀態(tài)總數(shù)(
state)是有限的。任一時(shí)刻,只處在一種狀態(tài)之中。
某種條件下,會從一種狀態(tài)轉(zhuǎn)變(
transition)到另一種狀態(tài)。
2.4 執(zhí)行邏輯

2.5 分類
2.5.1 識別器(接受器),也叫序列檢測器。輸入字符符號,產(chǎn)生一個(gè)二元輸出,“是”或“否”,來回答輸入是否被機(jī)器接受。

這個(gè)應(yīng)用在語言學(xué)中,如果語言中的所有字詞組都能為機(jī)器識別并接受,那么我們稱這門語言是正則語言(cf. Kleene的定理)。
再如下圖識別地址的狀態(tài)機(jī):
2.5.2 變換器
摩爾型有限狀態(tài)機(jī)(Moore機(jī)),輸出只依賴于當(dāng)前狀態(tài)。即:
次態(tài) = f(現(xiàn)態(tài),輸入),輸出 = f(現(xiàn)態(tài))
米利型有限狀態(tài)機(jī)(Mealy機(jī)),輸出依賴于當(dāng)前狀態(tài)和輸入。即:
次態(tài) = f(現(xiàn)態(tài),輸入),輸出 = f(現(xiàn)態(tài),輸入)
2.6 表示法
2.6.1 狀態(tài)圖
也叫狀態(tài)機(jī)圖,描述了一個(gè)對象在生命周期內(nèi)所經(jīng)歷的各種狀態(tài),以及引起狀態(tài)變化的事件。
基礎(chǔ)概念包括:狀態(tài)、事件、動(dòng)作、活動(dòng)、轉(zhuǎn)移、守衛(wèi)條件等

2.6.2 活動(dòng)圖
活動(dòng)圖是狀態(tài)機(jī)的另一種表現(xiàn)形式。用于為一個(gè)對象在其生命周期中的行為建模。
活動(dòng)圖是一種描述系統(tǒng)動(dòng)態(tài)行為的圖,它用于描述活動(dòng)的順序,展現(xiàn)從一個(gè)活動(dòng)到另一個(gè)活動(dòng)的控制流。
2.6.3 狀態(tài)轉(zhuǎn)移表
狀態(tài)轉(zhuǎn)移表是展示有限半自動(dòng)機(jī)或有限狀態(tài)自動(dòng)機(jī)基于當(dāng)前狀態(tài)和其他輸入,要移動(dòng)到什么狀態(tài)(或在非確定有限狀態(tài)自動(dòng)機(jī)情況下那些狀態(tài))的表格。
“狀態(tài)表”本質(zhì)上是其中某些輸入是當(dāng)前狀態(tài),而輸出包含與其他輸出在一起的下一個(gè)狀態(tài)的真值表。

三 狀態(tài)機(jī)在軟件領(lǐng)域的應(yīng)用
3.1 應(yīng)用場景
- 正則語言。正則表達(dá)式。正則表達(dá)式僅僅是用來表示語言規(guī)則的一種形式。為了讓機(jī)器理解正則表達(dá)式,我們需要通過程序來實(shí)現(xiàn)一種與正則表達(dá)式等價(jià)的結(jié)構(gòu),這種結(jié)構(gòu)就是狀態(tài)機(jī)。見《正則表達(dá)式DFA構(gòu)造方法》
例如:[a|b]*abb

- 編譯器。詞法分析、語法分析。見《編譯原理之詞法分析》

- 網(wǎng)絡(luò)協(xié)議。對于電信行業(yè)網(wǎng)絡(luò)核心軟件來說,“有限狀態(tài)機(jī)”思想是基石。如TCP狀態(tài)機(jī)(TCP Finite State Machine)

- 游戲設(shè)計(jì)。復(fù)雜的狀態(tài)、事件、動(dòng)作。游戲主邏輯、游戲大廳等具有復(fù)雜UI交互的類,都可以考慮使用狀態(tài)機(jī)來進(jìn)行代碼編寫,細(xì)分狀態(tài),保證代碼的健壯性,方便以后擴(kuò)展新的特性。例如:掛機(jī)時(shí)自動(dòng)刷怪。見《游戲開發(fā)之狀態(tài)機(jī)的實(shí)現(xiàn)與優(yōu)化》

-
web前端UI控制。前端頁面涉及到大量的dom狀態(tài)變更、事件交互、回調(diào)動(dòng)作等,這本身就歸為一個(gè)狀態(tài)機(jī)問題。當(dāng)然,你也可以使用有限狀態(tài)機(jī)的函數(shù)庫Javascript Finite State Machine,見
阮一峰的《JavaScript與有限狀態(tài)機(jī)》
var menu = {
// 當(dāng)前狀態(tài)
currentState: 'hide',
// 綁定事件
initialize: function() {
var self = this;
self.on("hover", self.transition);
},
// 狀態(tài)轉(zhuǎn)換
transition: function(event){
switch(this.currentState) {
case "hide":
this.currentState = 'show';
doSomething();
break;
case "show":
this.currentState = 'hide';
doSomething();
break;
default:
console.log('Invalid State!');
break;
}
}
};
-
前端框架React,Redux**。React 的主要思想是通過構(gòu)建可復(fù)用組件來構(gòu)建用戶界面。所謂組件其實(shí)就是React有限狀態(tài)機(jī),通過
狀態(tài)渲染對應(yīng)的界面,且每個(gè)組件都有自己的生命周期,它規(guī)定了組件的狀態(tài)和方法需要在哪個(gè)階段進(jìn)行改變和執(zhí)行。
// State.js
import React, { Component, PropTypes } from 'react';
/**
* 使用es6語法 定義一個(gè)State組件
*/
export default class State extends Component {
constructor(props) {
super(props);
this.state = { //初始化state
countnum:0,
};
}
/**
* 點(diǎn)擊事件方法 countnum+1
*/
_handlerEvent(){
this.setState({
countnum:this.state.countnum+1,
})
}
render() {
return (<div>
{this._renderView()}
</div>);
}
/**
* 渲染一個(gè)button組件
*/
_renderView(){
return(
<div>
<button onClick={this._handlerEvent.bind(this)}>
點(diǎn)擊{this.state.countnum}次
</button>
</div>
);
}
}
- 業(yè)務(wù)系統(tǒng)。業(yè)務(wù)系統(tǒng)的本質(zhì)就是描述真實(shí)的世界,所以幾乎所有的業(yè)務(wù)系統(tǒng)里都會有狀態(tài)機(jī)的身影。
例如1:購入流程
- ......
小結(jié):狀態(tài)機(jī)在軟件行業(yè)使用廣泛,它們都有一個(gè)共通的特點(diǎn),將所有的狀態(tài)、事件、動(dòng)作都抽離出來,對復(fù)雜的狀態(tài)遷移邏輯統(tǒng)一管理。狀態(tài)機(jī)讓復(fù)雜的問題變得直觀、簡單、易懂、解耦、易管理。
3.2 編碼中如何運(yùn)用狀態(tài)機(jī)
引例:空調(diào)工作機(jī)制簡化后的模型,如何編碼實(shí)現(xiàn)。
假設(shè)遙控器只有兩個(gè)按鈕,power電源鍵和cool制冷鍵。
空調(diào)的運(yùn)行呈現(xiàn)3個(gè)狀態(tài),停止/Off、僅送風(fēng)/FanOnly、制冷/Cool。
起始狀態(tài)為Off 。

- 方法一:
if-esle/switch-case模式
package com.mhc.sample;
import static com.mhc.sample.Aircon.Event.*;
import static com.mhc.sample.Aircon.State.*;
/**
* 空調(diào)
*
* @author xiaolong
* @date 18/6/11 下午5:54
*/
public class Aircon {
/**
* 空調(diào)當(dāng)前狀態(tài)
*/
private State currentState = OFF;
public void dispather(Event event) {
if (currentState == OFF) {
if(event == CLICK_POWER){
setCurrentState(FAN_ONLY);
doStartFan();
}
} else if (currentState == FAN_ONLY) {
if(event == CLICK_POWER){
setCurrentState(OFF);
doStopFan();
} else if (event == CLICK_COOL) {
setCurrentState(COOL);
doStartCool();
}
} else if(currentState == COOL){
if(event == CLICK_POWER){
setCurrentState(OFF);
doStopCool();
} else if (event == CLICK_COOL) {
setCurrentState(FAN_ONLY);
doStartFan();
}
}
}
private void doStartFan(){
System.out.println("start Fan");
}
private void doStopFan(){
System.out.println("stop Fan");
}
private void doStartCool(){
System.out.println("start Cool");
}
private void doStopCool(){
System.out.println("stop Cool");
}
private void setCurrentState(State currentState) {
this.currentState = currentState;
}
/**
* 空調(diào)狀態(tài)枚舉
*/
public enum State {
//關(guān)閉中狀態(tài)
OFF,
//送風(fēng)中狀態(tài)
FAN_ONLY,
//制冷中狀態(tài)
COOL
}
/**
* 空調(diào)事件枚舉
*/
public enum Event {
//點(diǎn)擊電源鍵
CLICK_POWER,
//點(diǎn)擊制冷鍵
CLICK_COOL
}
}
public void dispather(Event event) {
switch (currentState) {
case OFF:
switch (event) {
case CLICK_POWER:
setCurrentState(FAN_ONLY);
doStartFan();
break;
}
break;
case FAN_ONLY:
switch (event) {
case CLICK_POWER:
setCurrentState(OFF);
doStopFan();
break;
case CLICK_COOL:
setCurrentState(COOL);
doStartCool();
break;
}
break;
case COOL:
switch (event) {
case CLICK_POWER:
setCurrentState(OFF);
doStopCool();
break;
case CLICK_COOL:
setCurrentState(FAN_ONLY);
doStartFan();
break;
}
break;
}
}
缺點(diǎn):
a. 當(dāng)狀態(tài)很多的時(shí)候,維護(hù)起來非常麻煩,容易出錯(cuò)。
b. 不容易定位錯(cuò)誤,對于狀態(tài)的理解也不清晰。
c. 這段代碼沒有實(shí)現(xiàn)有限狀態(tài)機(jī)和具體事件動(dòng)作的隔離。
- 方法二:狀態(tài)遷移表法,使用數(shù)組與函數(shù)引用組合實(shí)現(xiàn)
package com.mhc.sample;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.mhc.sample.AirconTable.Event.*;
import static com.mhc.sample.AirconTable.State.*;
/**
* 空調(diào) - 狀態(tài)轉(zhuǎn)移表模式
*
* @author xiaolong
* @date 18/6/11 下午5:54
*/
public class AirconTable {
/**
* 狀態(tài)轉(zhuǎn)移表
*/
private List<Transfor> transforTable = new ArrayList<Transfor>() {
private static final long serialVersionUID = 2679742264102211454L;
{
add(Transfor.of( OFF, CLICK_POWER, FAN_ONLY, () -> doStartFan() ));
add(Transfor.of( FAN_ONLY, CLICK_POWER, OFF, () -> doStopFan() ));
add(Transfor.of( FAN_ONLY, CLICK_COOL, COOL, () -> doStartCool()));
add(Transfor.of( COOL, CLICK_POWER, OFF, () -> doStopCool() ));
add(Transfor.of( COOL, CLICK_COOL, FAN_ONLY, () -> doStartFan() ));
}
};
/**
* 空調(diào)當(dāng)前狀態(tài)
*/
private State currentState = OFF;
public void dispather(Event event) {
transforTable.forEach(transfor -> {
if(transfor.startState == currentState && transfor.event == event){
if(Objects.nonNull(transfor.doAction)){
transfor.doAction.run();
setCurrentState(transfor.nextState);
}
}
});
}
private void doStartFan() {
System.out.println("start Fan");
}
private void doStopFan() {
System.out.println("stop Fan");
}
private void doStartCool() {
System.out.println("start Cool");
}
private void doStopCool() {
System.out.println("stop Cool");
}
private void setCurrentState(State currentState) {
this.currentState = currentState;
}
/**
* 轉(zhuǎn)移
*/
static class Transfor {
//開始狀態(tài)
State startState;
//事件
Event event;
//目標(biāo)狀態(tài)
State nextState;
//執(zhí)行動(dòng)作
Runnable doAction;
static Transfor of(State startState, Event event, State nextState, Runnable doAction) {
Transfor transfor = new Transfor();
transfor.startState = startState;
transfor.nextState = nextState;
transfor.event = event;
transfor.doAction = doAction;
return transfor;
}
}
/**
* 空調(diào)狀態(tài)枚舉
*/
public enum State {
//關(guān)閉中狀態(tài)
OFF,
//送風(fēng)中狀態(tài)
FAN_ONLY,
//制冷中狀態(tài)
COOL
}
/**
* 空調(diào)事件枚舉
*/
public enum Event {
//點(diǎn)擊電源鍵
CLICK_POWER,
//點(diǎn)擊制冷鍵
CLICK_COOL
}
}
優(yōu)點(diǎn):
a. 狀態(tài)機(jī)可讀性比較好
b. 運(yùn)行時(shí)修改狀態(tài)表非常方便
c. 維護(hù)起來簡單
d. 可以實(shí)現(xiàn)多個(gè)狀態(tài)轉(zhuǎn)換表,根據(jù)需要加載不同的轉(zhuǎn)換表。
- 方法三:狀態(tài)模式法
狀態(tài)模式:允許對象在內(nèi)部狀態(tài)發(fā)生改變時(shí)改變它的行為,對象看起來好像修改了它的類。見《狀態(tài)模式》。
- 環(huán)境(Context)角色,也稱上下文:定義客戶端所感興趣的接口,并且保留一個(gè)具體狀態(tài)類的實(shí)例。這個(gè)具體狀態(tài)類的實(shí)例給出此環(huán)境對象的現(xiàn)有狀態(tài)。
- 抽象狀態(tài)(State)角色:定義一個(gè)接口,用以封裝環(huán)境(Context)對象的一個(gè)特定的狀態(tài)所對應(yīng)的行為。
- 具體狀態(tài)(ConcreteState)角色:每一個(gè)具體狀態(tài)類都實(shí)現(xiàn)了環(huán)境(Context)的一個(gè)狀態(tài)所對應(yīng)的行為。
類圖如下:

- 接口實(shí)現(xiàn)狀態(tài)模式
- Context類
package com.mhc.sample;
/**
* 狀態(tài)上下文
* @author xiaolong
* @date 18/6/12 下午12:02
*/
public class Context {
private State state;
public Context(State state){
setState(state);
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void request(Event event){
state.handle(this, event);
}
}
- State接口
package com.mhc.sample;
/**
* 狀態(tài)接口
* @author xiaolong
* @date 18/6/12 下午12:01
*/
public interface State {
/**
* 處理邏輯
* @param context
* @param event
*/
void handle(Context context, Event event);
}
- OffState類
package com.mhc.sample;
/**
* 關(guān)閉中狀態(tài)
* @author xiaolong
* @date 18/6/12 下午12:27
*/
public class OffState implements State {
@Override
public void handle(Context context, Event event) {
switch (event) {
case CLICK_POWER:
context.setState(new FanOnlyState());
doStartFan();
break;
}
}
private void doStartFan() {
System.out.println("start Fan");
}
}
- FanOnlyState類
package com.mhc.sample;
/**
* 送風(fēng)中狀態(tài)
* @author xiaolong
* @date 18/6/12 下午12:32
*/
public class FanOnlyState implements State {
@Override
public void handle(Context context, Event event) {
switch (event) {
case CLICK_POWER:
context.setState(new OffState());
doStopFan();
break;
case CLICK_COOL:
context.setState(new CoolState());
doStartCool();
break;
}
}
private void doStopFan(){
System.out.println("stop Fan");
}
private void doStartCool(){
System.out.println("start Cool");
}
}
- CoolState類
package com.mhc.sample;
/**
* 制冷中狀態(tài)
* @author xiaolong
* @date 18/6/12 下午12:27
*/
public class CoolState implements State {
@Override
public void handle(Context context, Event event) {
switch (event) {
case CLICK_POWER:
context.setState(new OffState());
doStopCool();
break;
case CLICK_COOL:
context.setState(new FanOnlyState());
doStartFan();
break;
}
}
private void doStartFan() {
System.out.println("start Fan");
}
private void doStopCool(){
System.out.println("stop Cool");
}
}
- Main類
package com.mhc.sample;
import static com.mhc.sample.Event.*;
/**
* @author xiaolong
* @date 18/6/12 下午12:40
*/
public class AirconMain {
public static void main(String[] args) {
State initState = new OffState();
Context context = new Context(initState);
context.request(CLICK_POWER);
System.out.println(context.getState().toString());
context.request(CLICK_COOL);
System.out.println(context.getState().toString());
context.request(CLICK_COOL);
System.out.println(context.getState().toString());
context.request(CLICK_POWER);
System.out.println(context.getState().toString());
}
}
- 枚舉實(shí)現(xiàn)狀態(tài)模式
- EnumStateContext類
package com.mhc.sample;
/**
* 狀態(tài)上下文
* @author xiaolong
* @date 18/6/12 下午12:02
*/
public class EnumStateContext {
private AirconStateEnum state;
public EnumStateContext(AirconStateEnum state){
setState(state);
}
public void setState(AirconStateEnum state) {
this.state = state;
}
public AirconStateEnum getState() {
return state;
}
public void request(Event event){
state.handle(this, event);
}
}
- AirconStateEnum類
package com.mhc.sample;
import static com.mhc.sample.Event.CLICK_COOL;
import static com.mhc.sample.Event.CLICK_POWER;
/**
* 枚舉實(shí)現(xiàn)狀態(tài)模式
* @author xiaolong
* @date 18/6/12 下午1:01
*/
public enum AirconStateEnum {
OFF {
@Override
void handle(EnumStateContext context, Event event) {
switch (event) {
case CLICK_POWER:
context.setState(FAN_NOLY);
super.doStartFan();
break;
}
}
},
FAN_NOLY {
@Override
void handle(EnumStateContext context, Event event) {
switch (event) {
case CLICK_POWER:
context.setState(OFF);
super.doStopFan();
break;
case CLICK_COOL:
context.setState(COOL);
super.doStartCool();
break;
}
}
},
COOL {
@Override
void handle(EnumStateContext context, Event event) {
switch (event) {
case CLICK_POWER:
context.setState(OFF);
super.doStopCool();
break;
case CLICK_COOL:
context.setState(FAN_NOLY);
super.doStartFan();
break;
}
}
};
abstract void handle(EnumStateContext context, Event event);
private void doStartFan(){
System.out.println("start Fan");
}
private void doStopFan(){
System.out.println("stop Fan");
}
private void doStartCool(){
System.out.println("start Cool");
}
private void doStopCool(){
System.out.println("stop Cool");
}
public static void main(String[] args) {
EnumStateContext context = new EnumStateContext(OFF);
context.request(CLICK_POWER);
System.out.println(context.getState().toString());
context.request(CLICK_COOL);
System.out.println(context.getState().toString());
context.request(CLICK_COOL);
System.out.println(context.getState().toString());
context.request(CLICK_POWER);
System.out.println(context.getState().toString());
}
}
優(yōu)點(diǎn):
a. 狀態(tài)維護(hù)方便
b. 擴(kuò)展性強(qiáng)
c. 解耦
- 方法四:開源框架法
現(xiàn)在狀態(tài)機(jī)開源框架也有不少。
- squirrel-foundation(702stars,a year ago)
- spring-statemachine(479stars,2 months ago)
- stateless4j(349stars,a month ago)
這三款FSM是github上stars top3的java狀態(tài)機(jī)引擎框架。至于如何技術(shù)選型,可參考《狀態(tài)機(jī)引擎選型》。因?yàn)?code>spring家族的statemachine活躍度和星級都還不錯(cuò),所以我果斷選擇它了。在下一章節(jié)我將詳細(xì)介紹其使用方法,如果想先睹為快,請轉(zhuǎn)到4.4章節(jié)Spring State Machine 例子
小結(jié):每種方法各有利弊,具體使用請結(jié)合實(shí)際場景。
四 Spring State Machine 介紹
4.1 項(xiàng)目概要
該項(xiàng)目自2015年啟動(dòng),已經(jīng)3歲啦。
Spring Statemachine(SSM)是基于Spring框架的、實(shí)現(xiàn)了狀態(tài)機(jī)概念的框架。 SSM旨在提供以下功能:
簡單易用,配置簡單
采用層次化狀態(tài)機(jī)結(jié)構(gòu)簡化復(fù)雜狀態(tài)配置
類型安全的適配器配置
與Spring Boot、Spring IOC友好集成,bean可以和狀態(tài)機(jī)交互
狀態(tài)機(jī)區(qū)域提供更復(fù)雜的狀態(tài)配置
實(shí)現(xiàn)了觸發(fā)器,遷移,警衛(wèi),動(dòng)作行為等概念
提供事件監(jiān)聽器
提供轉(zhuǎn)移攔截器
提供狀態(tài)機(jī)元配置動(dòng)態(tài)化支持
與Spring Security結(jié)合提供狀態(tài)機(jī)安全方面的配置
狀態(tài)機(jī)持久化支持Redis、JPA、Mongodb
狀態(tài)機(jī)測試支持
基于ZooKeeper實(shí)現(xiàn)的分布式狀態(tài)機(jī)
支持使用UI建模定義狀態(tài)機(jī)配置(Eclipse Papyrus插件)
開源項(xiàng)目模塊劃分如下:

4.2 使用場景
以下情況是使用狀態(tài)機(jī)的理想選擇:
應(yīng)用程序結(jié)構(gòu)的一部分可以表示為狀態(tài)。
你希望復(fù)雜的邏輯(如:if-else/swich-case)分成更小的可管理任務(wù)。
應(yīng)用程序已經(jīng)遭受異步的并發(fā)性問題。
如果你準(zhǔn)備實(shí)現(xiàn)一個(gè)狀態(tài)機(jī):
使用布爾標(biāo)記和枚舉模型的情況
對于某些應(yīng)用程序生命周期的一部分的有效變量
遍歷
if-else結(jié)構(gòu)設(shè)置特定標(biāo)示和枚舉
4.3 要素和基本概念

State Machine:將狀態(tài)、轉(zhuǎn)移、事件、動(dòng)作整合到一起管理的模型。State:一個(gè)有限的狀態(tài)模型,由事件驅(qū)動(dòng)其發(fā)生修改。Initial State:狀態(tài)機(jī)啟動(dòng)的特殊狀態(tài)。初始狀態(tài)總是綁定到特定的狀態(tài)機(jī)或區(qū)域。具有多個(gè)區(qū)域的狀態(tài)機(jī)可能具有多個(gè)初始狀態(tài)。End State:最終狀態(tài)是一種特殊的狀態(tài),表示封閉區(qū)域已完成。如果封閉區(qū)域直接包含在狀態(tài)機(jī)中,并且狀態(tài)機(jī)中的所有其他區(qū)域也都完成了,則表示整個(gè)狀態(tài)機(jī)已完成。History State:一種允許狀態(tài)機(jī)記住其最后活動(dòng)狀態(tài)的偽狀態(tài)。存在兩種類型的歷史狀態(tài),淺層僅記住頂層狀態(tài),深層記錄子機(jī)中的活動(dòng)狀態(tài)。Choice State:允許基于事件標(biāo)題或擴(kuò)展?fàn)顟B(tài)變量進(jìn)行轉(zhuǎn)換選擇的偽狀態(tài)。Fork State:一種偽狀態(tài),可以控制進(jìn)入某個(gè)區(qū)域。Join State:一個(gè)偽狀態(tài),它可以從一個(gè)區(qū)域提供受控的退出。Extended State:保存在狀態(tài)機(jī)中的一組特殊的變量。Transition:源狀態(tài)和目標(biāo)狀態(tài)之間的關(guān)系,由事件驅(qū)動(dòng)其轉(zhuǎn)移。Event:驅(qū)動(dòng)狀態(tài)發(fā)生遷移的事件,可以用枚舉或字符串描述。Region:區(qū)域是復(fù)合狀態(tài)或狀態(tài)機(jī)的正交部分。它包含狀態(tài)和轉(zhuǎn)換。Guard:是一個(gè)基于擴(kuò)展?fàn)顟B(tài)變量和事件參數(shù)值動(dòng)態(tài)計(jì)算的布爾表達(dá)式。保護(hù)條件僅在評估為TRUE時(shí)啟用操作或轉(zhuǎn)換,并在評估為FALSE時(shí)將其禁用,從而影響狀態(tài)機(jī)的行為。Action:動(dòng)作是在觸發(fā)轉(zhuǎn)換期間執(zhí)行的活動(dòng)行為。
4.4 Spring State Machine 例子
關(guān)于狀態(tài)機(jī)如何使用,官網(wǎng)例子有很多,我這里就不細(xì)說了,需要先睹簡單例子的請移駕官網(wǎng)。
在這里我分享下在生產(chǎn)環(huán)境如何優(yōu)雅的使用狀態(tài)機(jī)?
引例:物流系統(tǒng)訂單處理過程。該例子來自本人公司的Jac項(xiàng)目,如果您是內(nèi)部員工,請移駕gitlab。

- 項(xiàng)目架構(gòu)

- 狀態(tài)機(jī)目錄結(jié)構(gòu),后面會一一說明其功能和實(shí)現(xiàn)。

- 訂單事件枚舉類(OrderEvent)
package com.mhc.jac.service.core.statemachine.service.event;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 訂單事件
*
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/15 下午4:19
*/
@AllArgsConstructor
@Getter
public enum OrderEvent {
INIT_STATE("INIT_STATE", "訂單狀態(tài)初始化"),
RECEIVE_ORDER("RECEIVE_ORDER", "客服接單"),
SCHEDULE("SCHEDULE", "調(diào)度接單"),
COMPLETE("COMPLETE","完成訂單"),
CANCEL("CANCEL","取消訂單"),
CLOSE("CLOSE","關(guān)閉訂單"),
;
private String key;
private String desc;
}
- 訂單狀態(tài)枚舉(OrderState)
package com.mhc.jac.service.core.statemachine.service.state;
//import 省略
/**
* 訂單狀態(tài)
*
* @author xiaolong
* @Date 18/4/15 下午4:17
*/
@AllArgsConstructor
@Getter
@EnumAnnotation
public enum OrderState implements BaseEnum {
WAIT_SUBMIT(0,"WAIT_SUBMIT","待提交(草稿狀態(tài))"),
WAIT_RECEIVING(5,"WAIT_RECEIVING","待接單"),
WAIT_SCHEDULING(10,"WAIT_SCHEDULING","待調(diào)度"),
PROCESSING(15,"PROCESSING","進(jìn)行中"),
COMPLETED(20,"COMPLETED","已完成"),
CLOSED(99,"CLOSED","已關(guān)閉"),
CANCELED(100,"CANCELED","已取消"),
;
private Integer code;
private String key;
private String desc;
}
- 訂單狀態(tài)機(jī)配置(OrderStateMachineConfig)
package com.mhc.jac.service.core.statemachine.service.config.machine;
//import 省略
/**
* 訂單狀態(tài)機(jī)配置
* @author xiaolong
* @date 18/4/15 下午4:15
*/
@Configuration
@EnableStateMachineFactory(name="orderStateMachineFactory",contextEvents = false)
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
//訂單行為動(dòng)作(需要做的業(yè)務(wù))
@Autowired
private OrderAction orderAction;
//訂單狀態(tài)機(jī)監(jiān)聽器(也可以做相關(guān)的業(yè)務(wù))
@Autowired
private OrderStateMachineListener listener;
//日志監(jiān)聽器
@Autowired
private LogStateMachineListener<OrderState, OrderEvent> logStateMachineListener;
//狀態(tài)機(jī)運(yùn)行時(shí)持久化配置
@Autowired
private StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister;
@Override
public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config)
throws Exception {
config
.withConfiguration()
//注冊監(jiān)聽器
.listener(listener)
.listener(logStateMachineListener)
;
config.withPersistence()
//配置運(yùn)行時(shí)持久化對象
.runtimePersister(stateMachineRuntimePersister);
}
@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
//初始化訂單狀態(tài)
.initial(OrderState.WAIT_SUBMIT)
//有限訂單狀態(tài)集合
.states(EnumSet.allOf(OrderState.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
transitions
.withExternal()
//待提交 -> 待接單
.source(OrderState.WAIT_SUBMIT).target(OrderState.WAIT_RECEIVING)
//訂單狀態(tài)初始化事件
.event(OrderEvent.INIT_STATE)
.and()
.withExternal()
//待接單 -> 待調(diào)度
.source(OrderState.WAIT_RECEIVING).target(OrderState.WAIT_SCHEDULING)
//客服接單事件
.event(OrderEvent.RECEIVE_ORDER)
//接單業(yè)務(wù)
.action(Actions.withException(orderAction::agentAccept))
.and()
.withExternal()
//待接單 -> 已取消
.source(OrderState.WAIT_RECEIVING).target(OrderState.CANCELED)
//取消訂單事件
.event(OrderEvent.CANCEL)
//取消訂單業(yè)務(wù)
.action(Actions.withException(orderAction::cancelOrder))
.and()
.withExternal()
//待調(diào)度 -> 已取消
.source(OrderState.WAIT_SCHEDULING).target(OrderState.CANCELED)
//取消訂單事件
.event(OrderEvent.CANCEL)
//取消訂單業(yè)務(wù)
.action(orderAction::cancelOrder)
.and()
.withExternal()
//待調(diào)度 -> 進(jìn)行中
.source(OrderState.WAIT_SCHEDULING).target(OrderState.PROCESSING)
//調(diào)度接單事件
.event(OrderEvent.SCHEDULE)
//調(diào)度接單業(yè)務(wù)
.action(Actions.withException(orderAction::dispatcherAccept))
.and()
.withExternal()
//進(jìn)行中 -> 已關(guān)閉
.source(OrderState.PROCESSING).target(OrderState.CLOSED)
//關(guān)閉訂單事件
.event(OrderEvent.CLOSE)
//關(guān)閉訂單業(yè)務(wù)
.action(orderAction::closeOrder)
.and()
.withExternal()
//進(jìn)行中 -> 已完成
.source(OrderState.PROCESSING).target(OrderState.COMPLETED)
//完成訂單事件
.event(OrderEvent.COMPLETE);
}
}
- 訂單狀態(tài)機(jī)訂閱者(OrderStateMachineSubscriber),這里使用了Guava的事件總線
EventBus開源工具將業(yè)務(wù)與狀態(tài)機(jī)解耦,業(yè)務(wù)服務(wù)發(fā)布事件,由狀態(tài)機(jī)來訂閱。
package com.mhc.jac.service.core.bus.subscriber.statemachine;
//import 省略
/**
* 訂單狀態(tài)機(jī)訂閱者
*
* @author xiaolong
* @date 18/5/22 下午2:02
*/
@Component
@Slf4j
public class OrderStateMachineSubscriber {
//自定義的狀態(tài)機(jī)服務(wù)
@Autowired
private CustomStateMachineService<OrderState, OrderEvent> stateMachineService;
/**
* 訂單業(yè)務(wù)數(shù)據(jù)初始化完成時(shí),初始化訂單狀態(tài)
*
* @param orderInitFinishEvent
*/
@Subscribe
@AllowConcurrentEvents
public void initOrderStateMachine(OrderInitFinishEvent orderInitFinishEvent) {
//獲取訂單狀態(tài)機(jī)
StateMachine<OrderState, OrderEvent> stateMachine =
stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, orderInitFinishEvent.getOrderId());
//發(fā)送初始化狀態(tài)事件
stateMachine.sendEvent(OrderEvent.INIT_STATE);
}
/**
* 嘗試客服接單
*
* @param tryAgentAcceptEvent
*/
@Subscribe
@AllowConcurrentEvents
public void tryAgentAccept(TryAgentAcceptEvent tryAgentAcceptEvent) {
//獲取訂單狀態(tài)機(jī)
StateMachine<OrderState, OrderEvent> stateMachine =
stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryAgentAcceptEvent.getOrderId());
//給狀態(tài)機(jī)發(fā)送客服接單事件
Message<OrderEvent> message = MessageBuilder
.withPayload(OrderEvent.RECEIVE_ORDER)
.setHeader(ORDER_ID_T_LONG, tryAgentAcceptEvent.getOrderId())
.build();
stateMachine.sendEvent(message);
}
/**
* 嘗試調(diào)度接單
*
* @param tryDispatcherAcceptEvent
*/
@Subscribe
@AllowConcurrentEvents
public void tryDispatcherAccept(TryDispatcherAcceptEvent tryDispatcherAcceptEvent) {
//獲取訂單狀態(tài)機(jī)
StateMachine<OrderState, OrderEvent> stateMachine =
stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryDispatcherAcceptEvent.getOrderId());
//給狀態(tài)機(jī)發(fā)送調(diào)度接單事件
Message<OrderEvent> message = MessageBuilder
.withPayload(OrderEvent.SCHEDULE)
.setHeader(ORDER_ID_T_LONG, tryDispatcherAcceptEvent.getOrderId())
.build();
stateMachine.sendEvent(message);
//異常處理
Exception exception = StateMachineUtils.getExtraStateVariable(
stateMachine,
KeyConstant.STATE_MACHINE_ACTION_EXCEPTION_T_EXCEPTION
);
if (Objects.nonNull(exception)){
tryDispatcherAcceptEvent.setCallbackException(BaseEvent.CallbackException.of(exception));
}
}
/**
* 嘗試取消訂單
*
* @param tryCancelEvent
*/
@Subscribe
@AllowConcurrentEvents
public void tryCancelOrder(TryCancelEvent tryCancelEvent) {
//獲取訂單狀態(tài)機(jī)
StateMachine<OrderState, OrderEvent> stateMachine =
stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryCancelEvent.getOrderId());
//給狀態(tài)機(jī)發(fā)送取消訂單事件
Message<OrderEvent> message = MessageBuilder
.withPayload(OrderEvent.CANCEL)
.setHeader(ORDER_ID_T_LONG, tryCancelEvent.getOrderId())
.build();
stateMachine.sendEvent(message);
}
/**
* 嘗試關(guān)閉訂單
*
* @param tryCloseEvent
*/
@Subscribe
@AllowConcurrentEvents
public void tryCloseOrder(TryCloseEvent tryCloseEvent) {
//獲取訂單狀態(tài)機(jī)
StateMachine<OrderState, OrderEvent> stateMachine =
stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryCloseEvent.getOrderId());
//給狀態(tài)機(jī)發(fā)送關(guān)閉訂單事件
Message<OrderEvent> message = MessageBuilder
.withPayload(OrderEvent.CLOSE)
.setHeader(ORDER_ID_T_LONG, tryCloseEvent.getOrderId())
.build();
stateMachine.sendEvent(message);
}
}
- 訂單任務(wù)(OrderAction),主要調(diào)用訂單業(yè)務(wù)服務(wù)處理對應(yīng)的業(yè)務(wù)。注意Action中不要直接寫業(yè)務(wù)內(nèi)容,業(yè)務(wù)內(nèi)容由業(yè)務(wù)服務(wù)負(fù)責(zé)。
package com.mhc.jac.service.core.statemachine.service.action;
//import 省略
/**
* 訂單任務(wù)
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/16 下午11:35
*/
@Action
@Slf4j
public class OrderAction {
@Autowired
private OrderService orderService;
@Autowired
private EventBus eventBus;
public void changeStateAction2(StateContext<OrderState, OrderEvent> context) {
log.info("OrderAction changeStateAction2");
}
/**
* 客服接單
* @param context
*/
public void agentAccept(StateContext<OrderState, OrderEvent> context) {
MessageHeaders messageHeaders = context.getMessage().getHeaders();
Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);
orderService.agentAcceptOrder(orderId);
}
/**
* 調(diào)度接單
* @param context
*/
public void dispatcherAccept(StateContext<OrderState, OrderEvent> context) {
log.info("do action dispatcherAccept");
MessageHeaders messageHeaders = context.getMessage().getHeaders();
Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);
orderService.dispatcherAcceptOrder(orderId);
}
/**
* 取消訂單
* @param context
*/
public void cancelOrder(StateContext<OrderState, OrderEvent> context) {
MessageHeaders messageHeaders = context.getMessage().getHeaders();
Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);
//訂單取消成功后,發(fā)送訂單取消任務(wù)執(zhí)行完成事件
if(orderService.cancelOrder(orderId)){
CancelCompleteEvent cancelCompleteEvent = CancelCompleteEvent.builder()
.orderId(orderId)
.build();
cancelCompleteEvent.setFrom(OperatingEventEnum.ORDER_CANCEL.getDesc());
cancelCompleteEvent.setSendTime(LocalDateTime.now());
eventBus.post(cancelCompleteEvent);
}
}
/**
* 關(guān)閉訂單
* @param context
*/
public void closeOrder(StateContext<OrderState, OrderEvent> context) {
MessageHeaders messageHeaders = context.getMessage().getHeaders();
Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);
orderService.closeOrder(orderId);
}
}
- 訂單狀態(tài)機(jī)監(jiān)聽器(OrderStateMachineListener),目前沒有寫什么業(yè)務(wù)。
package com.mhc.jac.service.core.statemachine.service.listener;
//import 省略
import java.util.Objects;
/**
* 訂單狀態(tài)機(jī)監(jiān)聽器
*
* @author wangxiaolong <xiaolong@maihaoche.com>
* @ate 18/4/16 下午11:20
*/
@Listener
@Slf4j
public class OrderStateMachineListener extends StateMachineListenerAdapter<OrderState,OrderEvent> {
@Override
public void stateChanged(State<OrderState,OrderEvent> from, State<OrderState,OrderEvent> to) {
log.info("OrderStateMachineListener stateChanged,source:{},target:{}",from,to);
}
@Override
public void stateEntered(State<OrderState,OrderEvent> state) {
log.info("OrderStateMachineListener stateEntered,state:{}",state.getId());
}
@Override
public void stateExited(State<OrderState,OrderEvent> state) {
log.info("OrderStateMachineListener stateExited,state:{}",state.getId());
}
@Override
public void eventNotAccepted(Message<OrderEvent> event) {
log.info("OrderStateMachineListener eventNotAccepted,,event:{}",event.getPayload());
}
@Override
public void transition(Transition<OrderState,OrderEvent> transition) {
log.info("OrderStateMachineListener transition,source:{},target:{}",transition,transition.getTarget().getId());
}
@Override
public void transitionStarted(Transition<OrderState,OrderEvent> transition) {
log.info("OrderStateMachineListener transitionStarted,source:{},target:{}",transition,transition.getTarget().getId());
}
@Override
public void transitionEnded(Transition<OrderState,OrderEvent> transition) {
log.info("OrderStateMachineListener transitionEnded,source:{},target:{}",
transition.getSource(),Objects.nonNull(transition.getTarget()) ? transition.getTarget().getId() : "");
}
@Override
public void stateMachineStarted(StateMachine<OrderState,OrderEvent> stateMachine) {
log.info("OrderStateMachineListener stateMachineStarted");
}
@Override
public void stateMachineStopped(StateMachine<OrderState,OrderEvent> stateMachine) {
log.info("OrderStateMachineListener stateMachine");
}
@Override
public void stateMachineError(StateMachine<OrderState,OrderEvent> stateMachine, Exception exception) {
log.info("OrderStateMachineListener stateMachineError",exception);
}
@Override
public void extendedStateChanged(Object key, Object value) {
log.info("OrderStateMachineListener extendedStateChanged");
}
@Override
public void stateContext(StateContext<OrderState,OrderEvent> stateContext) {
//log.info("OrderStateMachineListener stateContext");
}
}
- 訂單狀態(tài)機(jī)基礎(chǔ)配置(OrderStateMachineBaseConfig),主要包括提供狀態(tài)機(jī)類型、訂單狀態(tài)更新方法、運(yùn)行時(shí)持久化配置、日志監(jiān)聽器、訂單狀態(tài)機(jī)管理服務(wù)。其繼承父類(StateMachineBaseConfig)。
package com.mhc.jac.service.core.statemachine.service.config.base;
//import 省略
/**
* @author wangxiaolong <xiaolong@maihaoche.com>
* @Date 18/4/21 下午10:19
*/
@Configuration
public class OrderStateMachineBaseConfig extends StateMachineBaseConfig<OrderState, OrderEvent> {
@Autowired
private OrderManager orderManager;
@Override
public StateMachineTypeEnum supplierStateMachineType() {
return StateMachineTypeEnum.ORDER;
}
@Override
public void saveBizState(CustomStateMachineContext<OrderState,OrderEvent> context){
Order o = new Order();
o.setOrderId(context.getBizId());
o.setOrderStatus(context.getState().getCode());
orderManager.updateById(o);
}
@Bean("orderLogStateMachineListener")
@Override
public LogStateMachineListener<OrderState,OrderEvent> logStateMachineListener(){
return super.logStateMachineListener();
}
@Bean("orderStateMachineRuntimePersister")
@Override
public StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository){
return super.stateMachineRuntimePersister(jpaStateMachineRepository);
}
@Bean("orderCustomStateMachineService")
@Override
public CustomStateMachineService<OrderState, OrderEvent> stateMachineService(StateMachineFactory<OrderState, OrderEvent> stateMachineFactory, StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister){
return super.stateMachineService(stateMachineFactory,stateMachineRuntimePersister);
}
}
- 狀態(tài)機(jī)基礎(chǔ)配置,抽象類(StateMachineBaseConfig)
package com.mhc.jac.service.core.statemachine.base.config;
//import 省略
/**
* 狀態(tài)機(jī)基礎(chǔ)配置
* @Author wangxiaolong <xiaolong@maihaoche.com>
* @Date 18/4/18 下午1:58
*/
public abstract class StateMachineBaseConfig<S extends Enum<S>, E extends Enum<E>> {
/**
* 獲取狀態(tài)機(jī)業(yè)務(wù)類型
* @return
*/
protected abstract StateMachineTypeEnum supplierStateMachineType();
/**
* 保存業(yè)務(wù)狀態(tài)
* @param context
*/
protected abstract void saveBizState(CustomStateMachineContext<S,E> context);
/**
* 保存業(yè)務(wù)狀態(tài)配置
* @return
*/
protected BizStatePersistingConfig<S, E> bizStatePersistingConfig() {
return BizStatePersistingConfig.<S, E>builder()
.saveBizSate(this::saveBizState)
.stateMachineType(supplierStateMachineType())
.build();
}
/**
* 狀態(tài)機(jī)器監(jiān)聽器記錄日志
* @return
*/
protected LogStateMachineListener<S,E> logStateMachineListener(){
return new LogStateMachineListener<>();
}
/**
* 狀態(tài)機(jī)運(yùn)行時(shí)持久化
* @param jpaStateMachineRepository
* @return
*/
protected StateMachineRuntimePersister<S,E,String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
BizStatePersistingConfig<S,E> bizStatePersistingConfig = bizStatePersistingConfig();
if(Objects.nonNull(bizStatePersistingConfig)){
return new CustomStateMachineRuntimePersister<>(jpaStateMachineRepository,bizStatePersistingConfig);
}
return new CustomStateMachineRuntimePersister<>(jpaStateMachineRepository);
}
/**
* 狀態(tài)機(jī)器交互統(tǒng)一服務(wù)
* @param stateMachineFactory
* @param stateMachineRuntimePersister
* @return
*/
protected CustomStateMachineService<S,E> stateMachineService(
StateMachineFactory<S,E> stateMachineFactory,
StateMachineRuntimePersister<S,E,String> stateMachineRuntimePersister) {
return new CustomStateMachineService<>(stateMachineFactory, stateMachineRuntimePersister);
}
}
- 業(yè)務(wù)狀態(tài)持久化配置(BizStatePersistingConfig)
package com.mhc.jac.service.core.statemachine.base.config;
//import 省略
/**
* 業(yè)務(wù)狀態(tài)持久化配置
* @author wangxiaolong <xiaolong@maihaoche.com>
* @Date 18/4/20 下午3:34
*/
@Getter
@Builder
public class BizStatePersistingConfig<S,E> {
/**
* 保存業(yè)務(wù)狀態(tài)的方法
*/
private Consumer<CustomStateMachineContext<S,E>> saveBizSate;
/**
* 狀態(tài)機(jī)業(yè)務(wù)類型
*/
private StateMachineTypeEnum stateMachineType;
}
- 自定義狀態(tài)機(jī)運(yùn)行時(shí)持久化(CustomStateMachineRuntimePersister
),該類繼承父類JpaPersistingStateMachineInterceptor,本質(zhì)上是狀態(tài)機(jī)攔截器,當(dāng)狀態(tài)改變時(shí)將狀態(tài)機(jī)上下文和業(yè)務(wù)狀態(tài)持久化到數(shù)據(jù)庫。
package com.mhc.jac.service.core.statemachine.base.custom;
//import 省略
/**
* 自定義狀態(tài)機(jī)運(yùn)行時(shí)持久化
*
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/20 下午3:20
*/
@Slf4j
public class CustomStateMachineRuntimePersister<S,E,T> extends JpaPersistingStateMachineInterceptor<S,E,T> {
/**
* 保存業(yè)務(wù)狀態(tài)的配置
*/
private BizStatePersistingConfig<S,E> bizStatePersistingConfig;
public CustomStateMachineRuntimePersister(JpaStateMachineRepository jpaStateMachineRepository) {
super(jpaStateMachineRepository);
}
public CustomStateMachineRuntimePersister(JpaStateMachineRepository jpaStateMachineRepository,BizStatePersistingConfig<S,E> bizStatePersistingConfig) {
this(jpaStateMachineRepository);
this.bizStatePersistingConfig = bizStatePersistingConfig;
}
@Override
public void write(StateMachineContext<S, E> context, T contextObj) throws Exception {
//回寫業(yè)務(wù)
if(Objects.nonNull(bizStatePersistingConfig) && Objects.nonNull(bizStatePersistingConfig.getSaveBizSate())){
CustomStateMachineContext<S,E> customStateMachineContext = new CustomStateMachineContext<>(
context.getState(),
context.getEvent(),
context.getEventHeaders(),
context.getExtendedState(),
getBizId(context.getId(),bizStatePersistingConfig.getStateMachineType()),
bizStatePersistingConfig.getStateMachineType()
);
bizStatePersistingConfig.getSaveBizSate().accept(customStateMachineContext);
}
//回寫狀態(tài)機(jī)
super.write(context, contextObj);
log.info("[Interceptor] Custom state machine runtime persister is success.");
}
private Long getBizId(String stateMachineId, StateMachineTypeEnum stateMachineType) {
String bizIdStr = stateMachineId.replace(stateMachineType.getCode()+"_","");
return Long.valueOf(bizIdStr);
}
}
- 自定義狀態(tài)機(jī)上下文(CustomStateMachineContext),包括業(yè)務(wù)ID和狀態(tài)機(jī)類型。狀態(tài)機(jī)ID是由 "業(yè)務(wù)類型_業(yè)務(wù)ID"組成。
package com.mhc.jac.service.core.statemachine.base.custom;
//import 省略
/**
* 自定義狀態(tài)機(jī)上下文
* @author wangxiaolong <xiaolong@maihaoche.com>
* @Date 18/4/20 下午4:30
*/
@Data
public class CustomStateMachineContext<S,E> extends DefaultStateMachineContext<S, E> {
private Long bizId;
private StateMachineTypeEnum stateMachineType;
public CustomStateMachineContext(S state, E event, Map<String, Object> eventHeaders, ExtendedState extendedState,Long bizId,StateMachineTypeEnum stateMachineType) {
super(state, event, eventHeaders, extendedState);
this.bizId = bizId;
this.stateMachineType = stateMachineType;
}
}
- 狀態(tài)機(jī)管理服務(wù)(CustomStateMachineService),包括狀態(tài)機(jī)的獲取和釋放。
package com.mhc.jac.service.core.statemachine.base.custom;
//import 省略
/**
* 狀態(tài)機(jī)管理服務(wù) (包括狀態(tài)機(jī)的獲取和釋放)
*
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/20 上午10:43
*/
@Slf4j
public class CustomStateMachineService<S,E> extends DefaultStateMachineService<S,E> {
/**
* 狀態(tài)機(jī)本地緩存
*/
private final Map<String, StateMachine<S, E>> machines = new ConcurrentReferenceHashMap<>(16,ConcurrentReferenceHashMap.ReferenceType.WEAK);
private StateMachinePersist<S, E, String> stateMachinePersist;
private final StateMachineFactory<S, E> stateMachineFactory;
public CustomStateMachineService(StateMachineFactory<S, E> stateMachineFactory, StateMachineRuntimePersister<S, E, String> stateMachineRuntimePersister) {
super(stateMachineFactory, stateMachineRuntimePersister);
this.stateMachinePersist = stateMachineRuntimePersister;
this.stateMachineFactory = stateMachineFactory;
}
public StateMachine<S, E> getStateMachine(StateMachineTypeEnum stateMachineType, Long bizId) {
Assert.notNull(stateMachineType,"狀態(tài)機(jī)類型不能為空");
Assert.notNull(bizId,"業(yè)務(wù)ID不能為空");
String machineId = stateMachineType.getCode().concat("_").concat(String.valueOf(bizId));
return acquireStateMachine(machineId);
}
@Override
public StateMachine<S, E> acquireStateMachine(String machineId) {
//嘗試釋放無效緩存
tryReleaseStateMachine(machineId);
return acquireStateMachine(machineId, true);
}
private void tryReleaseStateMachine(String machineId) {
StateMachine<S,E> stateMachine = machines.get(machineId);
if(Objects.isNull(stateMachine)) {
return;
}
//從數(shù)據(jù)庫獲取內(nèi)容上下文
StateMachineContext<S, E> stateMachineContext = getStateMachineContextFromDB(machineId);
//緩存失效
if(isInvalidCache(stateMachine, stateMachineContext)){
//釋放緩存
releaseStateMachine(machineId,true);
}
}
private boolean isInvalidCache(StateMachine<S, E> stateMachine, StateMachineContext<S, E> stateMachineContext) {
return Objects.nonNull(stateMachineContext) && !stateMachine.getState().getId().toString().equals(stateMachineContext.getState().toString());
}
private StateMachineContext<S, E> getStateMachineContextFromDB(String machineId) {
StateMachineContext<S, E> stateMachineContext = null;
if (Objects.nonNull(stateMachinePersist)) {
try {
stateMachineContext = stateMachinePersist.read(machineId);
} catch (Exception e) {
log.error("Error handling context", e);
throw new StateMachineException("Unable to read context from store", e);
}
}
return stateMachineContext;
}
@Override
public StateMachine<S, E> acquireStateMachine(String machineId, boolean start) {
log.info("Acquiring machine with id " + machineId);
StateMachine<S,E> stateMachine = machines.get(machineId);
if (stateMachine == null) {
log.info("Getting new machine from factory with id " + machineId);
stateMachine = stateMachineFactory.getStateMachine(machineId);
if (stateMachinePersist != null) {
try {
StateMachineContext<S, E> stateMachineContext = stateMachinePersist.read(machineId);
stateMachine = restoreStateMachine(stateMachine, stateMachineContext);
} catch (Exception e) {
log.error("Error handling context", e);
throw new StateMachineException("Unable to read context from store", e);
}
}
machines.put(machineId, stateMachine);
}
return handleStart(stateMachine, start);
}
@Override
public void releaseStateMachine(String machineId) {
log.info("Releasing machine with id " + machineId);
StateMachine<S, E> stateMachine = machines.remove(machineId);
if (stateMachine != null) {
log.info("Found machine with id " + machineId);
stateMachine.stop();
}
}
@Override
public void releaseStateMachine(String machineId, boolean stop) {
log.info("Releasing machine with id " + machineId);
StateMachine<S, E> stateMachine = machines.remove(machineId);
if (stateMachine != null) {
log.info("Found machine with id " + machineId);
handleStop(stateMachine, stop);
}
}
@Override
protected void doStop() {
log.info("Entering stop sequence, stopping all managed machines");
ArrayList<String> machineIds = new ArrayList<>(machines.keySet());
for (String machineId : machineIds) {
releaseStateMachine(machineId, true);
}
}
}
- Action工具類(Actions)
package com.mhc.jac.service.core.statemachine.base;
//import 省略
/**
* Actions
*
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/5/25 下午9:24
*/
@Slf4j
public class Actions {
/**
* 全局異常Action
* @param <S>
* @param <E>
* @return
*/
public static <S extends Enum<S>, E extends Enum<E>> Action<S, E> globalException(){
return stateContext -> {
log.warn("[stateMachine]: action exception", stateContext.getException());
//todo 異常預(yù)警通知等,或消息隊(duì)列處理
};
}
/**
* 構(gòu)建帶有異?;貓?zhí)的Action
* @param <S>
* @param <E>
* @return
*/
public static <S extends Enum<S>, E extends Enum<E>> Action<S, E> withException(Action<S, E> rawAction){
return stateContext -> {
try {
rawAction.execute(stateContext);
}
catch (Exception e) {
log.warn("[stateMachine]: callback action exception,回執(zhí)異常", stateContext.getException());
//通過擴(kuò)展屬性回執(zhí)異常
stateContext.getExtendedState()
.getVariables()
.put(KeyConstant.STATE_MACHINE_ACTION_EXCEPTION_T_EXCEPTION,e);
throw e;
}
};
}
}
- 狀態(tài)機(jī)工具類(StateMachineUtils)
package com.mhc.jac.service.core.statemachine.base;
//import 省略
/**
* 狀態(tài)機(jī)工具箱
*
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/5/25 下午10:08
*/
public class StateMachineUtils {
private StateMachineUtils(){}
/**
* 獲取擴(kuò)展?fàn)顟B(tài)
* @param stateMachine
* @param key
* @param <S>
* @param <T>
* @param <R>
* @return
*/
public static <S,T,R> R getExtraStateVariable(StateMachine<S,T> stateMachine, String key){
Object variable = stateMachine.getExtendedState()
.getVariables()
.get(key);
if (Objects.isNull(variable)) {
return null;
}
return (R)variable;
}
}
- 自定義注解(Action、Guard、Listener)
/**
* 狀態(tài)機(jī)任務(wù)定義
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/23 下午2:22
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Action {
}
/**
* 狀態(tài)機(jī)警衛(wèi)定義
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/23 下午2:22
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Guard {
}
/**
* 狀態(tài)機(jī)監(jiān)聽器定義
* @author wangxiaolong <xiaolong@maihaoche.com>
* @date 18/4/23 下午2:22
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Listener {
}
- 狀態(tài)機(jī)Maven依賴
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-data-jpa</artifactId>
<version>1.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>2.0.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
4.5 基本原理
- 核心模型
StateMachineStateConfigurer:狀態(tài)配置。
StateMachineTransitionConfigurer:遷移配置,可以定義狀態(tài)遷移接受的事件,以及相應(yīng)的action。
StateMachineConfigurationConfigurer:狀態(tài)機(jī)系統(tǒng)配置,包括action執(zhí)行器(spring statemachine實(shí)例可以配置多個(gè)event,存儲在內(nèi)部queue中,并通過sync/async executor執(zhí)行)、listener(事件監(jiān)聽器)等。
StateMachineListener:事件監(jiān)聽器(通過Spring的event機(jī)制實(shí)現(xiàn)),監(jiān)聽stateEntered(進(jìn)入狀態(tài))、stateExited(離開狀態(tài))、eventNotAccepted(事件無法響應(yīng))、transition(轉(zhuǎn)換)、transitionStarted(轉(zhuǎn)換開始)、transitionEnded(轉(zhuǎn)換結(jié)束)、stateMachineStarted(狀態(tài)機(jī)啟動(dòng))、stateMachineStopped(狀態(tài)機(jī)關(guān)閉)、stateMachineError(狀態(tài)機(jī)異常)等事件,借助listener可以追蹤狀態(tài)遷移過程。
StateMachineInterceptor:狀態(tài)攔截器,不同于StateMachineListener被動(dòng)監(jiān)聽,interceptor擁有可以改變狀態(tài)變化鏈的能力,主要在preEvent(事件預(yù)處理)、preStateChange(狀態(tài)變更的前置處理)、postStateChange(狀態(tài)變更的后置處理)、preTransition(轉(zhuǎn)化的前置處理)、postTransition(轉(zhuǎn)化的后置處理)、stateMachineError(異常處理)等執(zhí)行點(diǎn)生效,內(nèi)部的PersistingStateChangeInterceptor(狀態(tài)持久化)等都是基于這個(gè)擴(kuò)展協(xié)議生效的。
StateMachine狀態(tài)機(jī)實(shí)例,spring statemachine支持單例、工廠模式兩種方式創(chuàng)建,每個(gè)statemachine有一個(gè)獨(dú)有的machineId用于標(biāo)識machine實(shí)例;需要注意的是statemachine實(shí)例內(nèi)部存儲了當(dāng)前狀態(tài)機(jī)等上下文相關(guān)的屬性,因此這個(gè)實(shí)例不能夠被多線程共享。
- SSM工作機(jī)制

- SSM狀態(tài)遷移過程

4.6 使用狀態(tài)機(jī)基本原則
狀態(tài)機(jī)中(包括Action、Listener、Guard)強(qiáng)烈不建議直接寫業(yè)務(wù)內(nèi)容,應(yīng)該直接調(diào)用業(yè)務(wù)服務(wù),具體業(yè)務(wù)內(nèi)容由業(yè)務(wù)服務(wù)實(shí)現(xiàn)。這樣可以實(shí)現(xiàn)狀態(tài)機(jī)與業(yè)務(wù)細(xì)節(jié)解耦。
基于事件的驅(qū)動(dòng)模型,業(yè)務(wù)通過消息驅(qū)動(dòng)狀態(tài)機(jī)。
業(yè)務(wù)主流程脈絡(luò)由狀態(tài)機(jī)統(tǒng)一管理。
狀態(tài)機(jī)之間不能直接調(diào)用,需要通過消息驅(qū)動(dòng)。
4.7 值得思考
狀態(tài)機(jī)異常處理機(jī)制如何優(yōu)雅處理?
事務(wù)怎么處理?
狀態(tài)機(jī)粒度如何切分?
五 狀態(tài)機(jī)是一種思維方式
瞧,對于我們?nèi)粘K玫拿钍骄幊?,那些?fù)雜的、冗長的if-else業(yè)務(wù),難以維護(hù)和擴(kuò)展,每次業(yè)務(wù)變更修改代碼時(shí)總是如履薄冰,為什么會這樣呢?
無非幾點(diǎn):
業(yè)務(wù)狀態(tài)多
if-else層次多而復(fù)雜業(yè)務(wù)處理過程復(fù)雜
業(yè)務(wù)相互嵌套,耦合性強(qiáng)
那你是否能從復(fù)雜的if-else中進(jìn)行分析、抽象,抽象出狀態(tài)、事件、動(dòng)作的概念,然后對它們統(tǒng)一管理,包裝出一個(gè)全新的概念-狀態(tài)機(jī)。
從小的角度來說,狀態(tài)機(jī)是一種對象行為建模的工具。使用對象有一個(gè)明確并且復(fù)雜的生命流(3個(gè)以上狀態(tài)),并且狀態(tài)變遷存在不同的觸發(fā)條件和處理行為。
從大的角度來說,這其實(shí)是一種全新的編程范式-面向狀態(tài)機(jī)編程。將狀態(tài)機(jī)提升到框架緯度,整個(gè)系統(tǒng)是由N臺狀態(tài)機(jī)組成,每臺狀態(tài)機(jī)訂閱著自己感興趣的事件,管理著自己的狀態(tài)和行為動(dòng)作,各司其職。它們之間通過事件相互驅(qū)動(dòng)各自的流轉(zhuǎn),整個(gè)業(yè)務(wù)就在流轉(zhuǎn)中完成。
從宏觀角度來說,整個(gè)宇宙就是一臺巨大的狀態(tài)機(jī),人類探索宇宙的奧秘,其實(shí)是在探索這臺機(jī)器的運(yùn)行機(jī)制。萬事萬物皆是狀態(tài)機(jī),小到細(xì)胞的新陳代謝,大腦中神經(jīng)元的交互,大到地球的生態(tài)圈,風(fēng)云變幻......
親,你Get到了嗎?
參考資料:
https://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA
https://blog.csdn.net/napoay/article/details/78071286
http://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html