狀態(tài)機(jī)思維

目錄


一. 背景

二. 概念

  • 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é)模型。

有限狀態(tài)機(jī).png

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í)行邏輯

狀態(tài)機(jī)執(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))

Moore機(jī)

米利型有限狀態(tài)機(jī)(Mealy機(jī))咙俩,輸出依賴于當(dāng)前狀態(tài)和輸入。即:


次態(tài) = f(現(xiàn)態(tài),輸入)湿故,輸出 = f(現(xiàn)態(tài),輸入)

Mealy機(jī)

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)條件等

學(xué)生狀態(tài)機(jī)圖

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)轉(zhuǎn)移表.png

三 狀態(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

正則表達(dá)式的NFA
詞法分析的基本步驟
  • 網(wǎng)絡(luò)協(xié)議捍掺。對于電信行業(yè)網(wǎng)絡(luò)核心軟件來說挺勿,“有限狀態(tài)機(jī)”思想是基石呛梆。如TCP狀態(tài)機(jī)(TCP Finite State Machine
The TCP Finite State Machine (FSM)
  • 游戲設(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)化》
角色自動關(guān)機(jī)狀態(tài)圖

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;

      }

    }

  }; 

  • 前端框架ReactRedux**雅任。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:購入流程

購入流程狀態(tài)圖
  • ......

小結(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)翰意。

  1. 假設(shè)遙控器只有兩個按鈕剃浇,power電源鍵和cool制冷鍵巾兆。

  2. 空調(diào)的運(yùn)行呈現(xiàn)3個狀態(tài),停止/Off虎囚、僅送風(fēng)/FanOnly角塑、制冷/Cool。

  3. 起始狀態(tài)為Off 淘讥。

空調(diào)工作狀態(tài)圖.png
  • 方法一: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)的行為伊履。

類圖如下:

狀態(tài)模式類圖
  1. 接口實(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());
    }
}

  1. 枚舉實(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)

這三款FSMgithub上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)目模塊劃分如下:

模塊劃分.png

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 要素和基本概念

Order Shipping
  • 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猛频。

訂單狀態(tài)機(jī)圖.png
  • 項(xiàng)目架構(gòu)
項(xiàng)目架構(gòu).png
  • 狀態(tài)機(jī)目錄結(jié)構(gòu)狮崩,后面會一一說明其功能和實(shí)現(xiàn)。
狀態(tài)機(jī)目錄.png
  • 訂單事件枚舉類(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工作原理.jpeg
  • SSM狀態(tài)遷移過程
轉(zhuǎn)態(tài)機(jī)狀態(tài)遷移過程.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冠蒋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乾胶,更是在濱河造成了極大的恐慌抖剿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件识窿,死亡現(xiàn)場離奇詭異斩郎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)喻频,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門缩宜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人半抱,你說我怎么就攤上這事脓恕∧に危” “怎么了窿侈?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秋茫。 經(jīng)常有香客問我史简,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任圆兵,我火速辦了婚禮跺讯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘殉农。我一直安慰自己刀脏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布超凳。 她就那樣靜靜地躺著愈污,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轮傍。 梳的紋絲不亂的頭發(fā)上暂雹,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音创夜,去河邊找鬼杭跪。 笑死,一個胖子當(dāng)著我的面吹牛驰吓,可吹牛的內(nèi)容都是我干的涧尿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼檬贰,長吁一口氣:“原來是場噩夢啊……” “哼现斋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起偎蘸,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤庄蹋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后迷雪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體限书,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年章咧,在試婚紗的時候發(fā)現(xiàn)自己被綠了倦西。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡赁严,死狀恐怖扰柠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疼约,我是刑警寧澤卤档,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站程剥,受9級特大地震影響劝枣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一舔腾、第九天 我趴在偏房一處隱蔽的房頂上張望溪胶。 院中可真熱鬧,春花似錦稳诚、人聲如沸哗脖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懒熙。三九已至,卻和暖如春普办,著一層夾襖步出監(jiān)牢的瞬間工扎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工衔蹲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肢娘,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓舆驶,卻偏偏與公主長得像橱健,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沙廉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理拘荡,服務(wù)發(fā)現(xiàn),斷路器撬陵,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架珊皿,建立于...
    Hsinwong閱讀 22,313評論 1 92
  • 牛骨熬的湯散發(fā)著奇異的魅力,誘人的香味布滿了整間小店巨税。食客們?nèi)齼蓛审ǎ吐曈懻撝髯缘脑掝},店老板帶著西北口音喊出...
    Yatoomi閱讀 785評論 0 0
  • 胡班!您的變化我都看到了!您的鐵人精神我也見識到了! 想起1.0我對您的陌生草添,那真是遙不可及的事情驶兜,我以為您永遠(yuǎn)那...
    hard_d724閱讀 347評論 3 6