目錄
一. 背景
二. 概念
1.1 狀態(tài)機(jī)模型的概念
2.2 組成要素
3.3 三個特征
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)信號動作、完成特定操作的控制中心撇簿。
狀態(tài)機(jī)的概念其實(shí)已經(jīng)很老了聂渊,有限自動機(jī)的描述可以追溯到1943年,當(dāng)時 Warren McCulloch 和 Walter Pitts 先生寫了一篇關(guān)于它的論文四瘫。后來汉嗽,George H.Mealy 在1955年提出了一個狀態(tài)機(jī)概念,稱為Mealy機(jī)
莲组。一年后的1956年,Edward F.Moore 提出了另一篇被稱為Moore機(jī)
的論文暖夭。后來這個概念被廣泛應(yīng)用于語言學(xué)锹杈、計算機(jī)科學(xué)撵孤、生物學(xué)、數(shù)學(xué)和邏輯學(xué)竭望,甚至于哲學(xué)等各種領(lǐng)域邪码。
在計算機(jī)科學(xué)中,有限狀態(tài)機(jī)被廣泛用于應(yīng)用行為建模咬清、硬件電路系統(tǒng)設(shè)計闭专、軟件工程,編譯器旧烧、網(wǎng)絡(luò)協(xié)議影钉、和計算與語言的研究。
今天我們來聊聊狀態(tài)機(jī)思維掘剪,以及它在計算機(jī)軟件開發(fā)領(lǐng)域中的應(yīng)用平委。
二 概念
2.1 狀態(tài)機(jī)模型的概念
有限狀態(tài)機(jī)
(英語:finite-state machine,縮寫:FSM
)又稱有限狀態(tài)自動機(jī)
夺谁,簡稱狀態(tài)機(jī)
廉赔,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。
2.2 組成要素
現(xiàn)態(tài):是指當(dāng)前所處的狀態(tài)匾鸥。
條件:又稱為
事件
蜡塌。當(dāng)一個條件被滿足,可能將會觸發(fā)一個動作勿负,或者執(zhí)行一次狀態(tài)的遷移
馏艾。動作:條件滿足后執(zhí)行的
動作
行為。動作執(zhí)行完畢后笆环,可以遷移到新的狀態(tài)攒至,也可以仍舊保持原狀態(tài)。動作不是必需的躁劣,當(dāng)條件滿足后迫吐,也可以不執(zhí)行任何動作,直接遷移到新狀態(tài)账忘。*次態(tài):條件滿足后要遷往的新狀態(tài)志膀。“次態(tài)”是相對于“現(xiàn)態(tài)”而言的鳖擒,“次態(tài)”一旦被激活溉浙,就轉(zhuǎn)變成新的“現(xiàn)態(tài)”了。
2.3 三個特征
狀態(tài)總數(shù)(
state
)是有限的蒋荚。任一時刻戳稽,只處在一種狀態(tài)之中。
某種條件下,會從一種狀態(tài)轉(zhuǎn)變(
transition
)到另一種狀態(tài)惊奇。
2.4 執(zhí)行邏輯
2.5 分類
2.5.1 識別器(接受器)
互躬,也叫序列檢測器
。輸入字符符號颂郎,產(chǎn)生一個二元輸出吼渡,“是”或“否”,來回答輸入是否被機(jī)器接受乓序。
這個應(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ī)圖阿趁,描述了一個對象在生命周期內(nèi)所經(jīng)歷的各種狀態(tài),以及引起狀態(tài)變化的事件坛猪。
基礎(chǔ)概念包括:狀態(tài)脖阵、事件、動作墅茉、活動命黔、轉(zhuǎn)移、守衛(wèi)條件等
2.6.2 活動圖
活動圖是狀態(tài)機(jī)的另一種表現(xiàn)形式就斤。用于為一個對象在其生命周期中的行為建模悍募。
活動圖是一種描述系統(tǒng)動態(tài)行為的圖,它用于描述活動的順序洋机,展現(xiàn)從一個活動到另一個活動的控制流坠宴。
2.6.3 狀態(tài)轉(zhuǎn)移表
狀態(tài)轉(zhuǎn)移表是展示有限半自動機(jī)或有限狀態(tài)自動機(jī)基于當(dāng)前狀態(tài)和其他輸入,要移動到什么狀態(tài)(或在非確定有限狀態(tài)自動機(jī)情況下那些狀態(tài))的表格绷旗。
“狀態(tài)表”本質(zhì)上是其中某些輸入是當(dāng)前狀態(tài)喜鼓,而輸出包含與其他輸出在一起的下一個狀態(tài)的真值表。
三 狀態(tài)機(jī)在軟件領(lǐng)域的應(yīng)用
3.1 應(yīng)用場景
- 正則語言衔肢。正則表達(dá)式庄岖。正則表達(dá)式僅僅是用來表示語言規(guī)則的一種形式。為了讓機(jī)器理解正則表達(dá)式角骤,我們需要通過程序來實(shí)現(xiàn)一種與正則表達(dá)式等價的結(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è)計徒河。復(fù)雜的狀態(tài)、事件筷厘、動作鸣峭。游戲主邏輯、游戲大廳等具有復(fù)雜UI交互的類酥艳,都可以考慮使用狀態(tài)機(jī)來進(jìn)行代碼編寫摊溶,細(xì)分狀態(tài),保證代碼的健壯性充石,方便以后擴(kuò)展新的特性莫换。例如:掛機(jī)時自動刷怪。見《游戲開發(fā)之狀態(tài)機(jī)的實(shí)現(xiàn)與優(yōu)化》
-
web前端UI控制骤铃。前端頁面涉及到大量的dom狀態(tài)變更浓镜、事件交互、回調(diào)動作等劲厌,這本身就歸為一個狀態(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)的界面沪么,且每個組件都有自己的生命周期硼婿,它規(guī)定了組件的狀態(tài)和方法需要在哪個階段進(jìn)行改變和執(zhí)行。
// State.js
import React, { Component, PropTypes } from 'react';
/**
* 使用es6語法 定義一個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>);
}
/**
* 渲染一個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è)使用廣泛殉摔,它們都有一個共通的特點(diǎn)州胳,將所有的狀態(tài)、事件逸月、動作都抽離出來栓撞,對復(fù)雜的狀態(tài)遷移邏輯統(tǒng)一管理。狀態(tài)機(jī)讓復(fù)雜的問題變得直觀碗硬、簡單瓤湘、易懂、解耦恩尾、易管理弛说。
3.2 編碼中如何運(yùn)用狀態(tài)機(jī)
引例:空調(diào)工作機(jī)制簡化后的模型,如何編碼實(shí)現(xiàn)翰意。
假設(shè)遙控器只有兩個按鈕剃浇,power電源鍵和cool制冷鍵巾兆。
空調(diào)的運(yùn)行呈現(xiàn)3個狀態(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)很多的時候圃伶,維護(hù)起來非常麻煩,容易出錯蒲列。
b. 不容易定位錯誤窒朋,對于狀態(tài)的理解也不清晰。
c. 這段代碼沒有實(shí)現(xiàn)有限狀態(tài)機(jī)和具體事件動作的隔離蝗岖。
- 方法二:狀態(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í)行動作
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)行時修改狀態(tài)表非常方便
c. 維護(hù)起來簡單
d. 可以實(shí)現(xiàn)多個狀態(tài)轉(zhuǎn)換表,根據(jù)需要加載不同的轉(zhuǎn)換表抵赢。
- 方法三:狀態(tài)模式法
狀態(tài)模式
:允許對象在內(nèi)部狀態(tài)發(fā)生改變時改變它的行為欺劳,對象看起來好像修改了它的類。見《狀態(tài)模式》铅鲤。
- 環(huán)境(Context)角色划提,也稱上下文:定義客戶端所感興趣的接口,并且保留一個具體狀態(tài)類的實(shí)例邢享。這個具體狀態(tài)類的實(shí)例給出此環(huán)境對象的現(xiàn)有狀態(tài)鹏往。
- 抽象狀態(tài)(State)角色:定義一個接口,用以封裝環(huán)境(Context)對象的一個特定的狀態(tài)所對應(yīng)的行為骇塘。
- 具體狀態(tài)(ConcreteState)角色:每一個具體狀態(tài)類都實(shí)現(xiàn)了環(huán)境(Context)的一個狀態(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活躍度和星級都還不錯奠货,所以我果斷選擇它了介褥。在下一章節(jié)我將詳細(xì)介紹其使用方法,如果想先睹為快递惋,請轉(zhuǎn)到4.4章節(jié)Spring State Machine 例子
小結(jié):每種方法各有利弊柔滔,具體使用請結(jié)合實(shí)際場景。
四 Spring State Machine 介紹
4.1 項(xiàng)目概要
該項(xiàng)目自2015年啟動萍虽,已經(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),動作行為等概念
提供事件監(jiān)聽器
提供轉(zhuǎn)移攔截器
提供狀態(tài)機(jī)元配置動態(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)一個狀態(tài)機(jī):
使用布爾標(biāo)記和枚舉模型的情況
對于某些應(yīng)用程序生命周期的一部分的有效變量
遍歷
if-else
結(jié)構(gòu)設(shè)置特定標(biāo)示和枚舉
4.3 要素和基本概念
State Machine
:將狀態(tài)、轉(zhuǎn)移戳寸、事件呈驶、動作整合到一起管理的模型。State
:一個有限的狀態(tài)模型疫鹊,由事件驅(qū)動其發(fā)生修改袖瞻。Initial State
:狀態(tài)機(jī)啟動的特殊狀態(tài)。初始狀態(tài)總是綁定到特定的狀態(tài)機(jī)或區(qū)域订晌。具有多個區(qū)域的狀態(tài)機(jī)可能具有多個初始狀態(tài)。End State
:最終狀態(tài)是一種特殊的狀態(tài)蚌吸,表示封閉區(qū)域已完成锈拨。如果封閉區(qū)域直接包含在狀態(tài)機(jī)中,并且狀態(tài)機(jī)中的所有其他區(qū)域也都完成了羹唠,則表示整個狀態(tài)機(jī)已完成奕枢。History State
:一種允許狀態(tài)機(jī)記住其最后活動狀態(tài)的偽狀態(tài)。存在兩種類型的歷史狀態(tài)佩微,淺層僅記住頂層狀態(tài)缝彬,深層記錄子機(jī)中的活動狀態(tài)。Choice State
:允許基于事件標(biāo)題或擴(kuò)展?fàn)顟B(tài)變量進(jìn)行轉(zhuǎn)換選擇的偽狀態(tài)哺眯。Fork State
:一種偽狀態(tài)谷浅,可以控制進(jìn)入某個區(qū)域。Join State
:一個偽狀態(tài)奶卓,它可以從一個區(qū)域提供受控的退出一疯。Extended State
:保存在狀態(tài)機(jī)中的一組特殊的變量。Transition
:源狀態(tài)和目標(biāo)狀態(tài)之間的關(guān)系夺姑,由事件驅(qū)動其轉(zhuǎn)移墩邀。Event
:驅(qū)動狀態(tài)發(fā)生遷移的事件,可以用枚舉或字符串描述盏浙。Region
:區(qū)域是復(fù)合狀態(tài)或狀態(tài)機(jī)的正交部分眉睹。它包含狀態(tài)和轉(zhuǎn)換荔茬。Guard
:是一個基于擴(kuò)展?fàn)顟B(tài)變量和事件參數(shù)值動態(tài)計算的布爾表達(dá)式。保護(hù)條件僅在評估為TRUE時啟用操作或轉(zhuǎn)換竹海,并在評估為FALSE時將其禁用慕蔚,從而影響狀態(tài)機(jī)的行為。Action
:動作是在觸發(fā)轉(zhuǎn)換期間執(zhí)行的活動行為站削。
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> {
//訂單行為動作(需要做的業(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)行時持久化配置
@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)行時持久化對象
.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ù)初始化完成時坦敌,初始化訂單狀態(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)行時持久化配置仗嗦、日志監(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)行時持久化
* @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)行時持久化(CustomStateMachineRuntimePersister
),該類繼承父類JpaPersistingStateMachineInterceptor丹弱,本質(zhì)上是狀態(tài)機(jī)攔截器德撬,當(dāng)狀態(tài)改變時將狀態(tài)機(jī)上下文和業(yè)務(wù)狀態(tài)持久化到數(shù)據(jù)庫铲咨。
package com.mhc.jac.service.core.statemachine.base.custom;
//import 省略
/**
* 自定義狀態(tài)機(jī)運(yùn)行時持久化
*
* @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í)例可以配置多個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ī)啟動)吹由、stateMachineStopped(狀態(tài)機(jī)關(guān)閉)、stateMachineError(狀態(tài)機(jī)異常)等事件朱嘴,借助listener可以追蹤狀態(tài)遷移過程倾鲫。
StateMachineInterceptor
:狀態(tài)攔截器,不同于StateMachineListener被動監(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)持久化)等都是基于這個擴(kuò)展協(xié)議生效的。
StateMachine
狀態(tài)機(jī)實(shí)例木柬,spring statemachine支持單例、工廠模式兩種方式創(chuàng)建,每個statemachine有一個獨(dú)有的machineId用于標(biāo)識machine實(shí)例哗魂;需要注意的是statemachine實(shí)例內(nèi)部存儲了當(dāng)前狀態(tài)機(jī)等上下文相關(guān)的屬性,因此這個實(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ū)動模型往踢,業(yè)務(wù)通過消息驅(qū)動狀態(tài)機(jī)。
業(yè)務(wù)主流程脈絡(luò)由狀態(tài)機(jī)統(tǒng)一管理徘层。
狀態(tài)機(jī)之間不能直接調(diào)用峻呕,需要通過消息驅(qū)動。
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ù)變更修改代碼時總是如履薄冰,為什么會這樣呢拥褂?
無非幾點(diǎn):
業(yè)務(wù)狀態(tài)多
if-else
層次多而復(fù)雜業(yè)務(wù)處理過程復(fù)雜
業(yè)務(wù)相互嵌套娘锁,耦合性強(qiáng)
那你是否能從復(fù)雜的if-else
中進(jìn)行分析、抽象饺鹃,抽象出狀態(tài)
莫秆、事件
、動作
的概念悔详,然后對它們統(tǒng)一管理镊屎,包裝出一個全新的概念-狀態(tài)機(jī)
。
從小的角度來說茄螃,狀態(tài)機(jī)是一種對象行為建模的工具缝驳。使用對象有一個明確并且復(fù)雜的生命流(3個以上狀態(tài)),并且狀態(tài)變遷存在不同的觸發(fā)條件和處理行為。
從大的角度來說用狱,這其實(shí)是一種全新的編程范式-面向狀態(tài)機(jī)編程萎庭。將狀態(tài)機(jī)提升到框架緯度,整個系統(tǒng)是由N臺狀態(tài)機(jī)組成齿拂,每臺狀態(tài)機(jī)訂閱著自己感興趣的事件驳规,管理著自己的狀態(tài)和行為動作,各司其職署海。它們之間通過事件相互驅(qū)動各自的流轉(zhuǎn)吗购,整個業(yè)務(wù)就在流轉(zhuǎn)中完成。
從宏觀角度來說砸狞,整個宇宙就是一臺巨大的狀態(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