状态机思维

目录


一. 背景

二. 概念

  • 1.1 状态机模型的概念

  • 2.2 组成要素

  • 3.3 三个特征

  • 4.4 执行逻辑

  • 5.5 分类

  • 6.6 表示法

三. 状态机在软件领域的应用

  • 3.1 应用场景

  • 3.2 编码中如何运用状态机

四. Spring State Machine 介绍

  • 4.1 项目概要

  • 4.2 使用场景

  • 4.3 要素和基本概念

  • 4.4 例子

  • 4.5 基本原理

  • 4.6 使用状态机基本原则

  • 4.7 值得思考

五. 状态机是一种思维方式


一 背景

有限状态机FSM(Finite State Machine),相信有些读者听说过,或者使用过。但是了解的人似乎并不多。

在硬件领域,状态机是由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。

状态机的概念其实已经很老了,有限自动机的描述可以追溯到1943年,当时 Warren McCulloch 和 Walter Pitts 先生写了一篇关于它的论文。后来,George H.Mealy 在1955年提出了一个状态机概念,称为Mealy机。一年后的1956年,Edward F.Moore 提出了另一篇被称为Moore机的论文。后来这个概念被广泛应用于语言学、计算机科学、生物学、数学和逻辑学,甚至于哲学等各种领域。

在计算机科学中,有限状态机被广泛用于应用行为建模、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究。

今天我们来聊聊状态机思维,以及它在计算机软件开发领域中的应用。

二 概念

2.1 状态机模型的概念

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

有限状态机.png

2.2 组成要素

  • 现态:是指当前所处的状态。

  • 条件:又称为事件。当一个条件被满足,可能将会触发一个动作,或者执行一次状态的迁移

  • 动作:条件满足后执行的动作行为。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

  • *次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

2.3 三个特征

  • 状态总数(state)是有限的。

  • 任一时刻,只处在一种状态之中。

  • 某种条件下,会从一种状态转变(transition)到另一种状态。

2.4 执行逻辑

状态机执行逻辑

2.5 分类

2.5.1 识别器(接受器),也叫序列检测器。输入字符符号,产生一个二元输出,“是”或“否”,来回答输入是否被机器接受。

这个应用在语言学中,如果语言中的所有字词组都能为机器识别并接受,那么我们称这门语言是正则语言cf. Kleene的定理)。

再如下图识别地址的状态机:

地址识别器

2.5.2 变换器

摩尔型有限状态机(Moore机),输出只依赖于当前状态。即:


次态 = f(现态,输入),输出 = f(现态)

Moore机

米利型有限状态机(Mealy机),输出依赖于当前状态和输入。即:


次态 = f(现态,输入),输出 = f(现态,输入)

Mealy机

2.6 表示法

2.6.1 状态图

  • 也叫状态机图,描述了一个对象在生命周期内所经历的各种状态,以及引起状态变化的事件。

  • 基础概念包括:状态、事件、动作、活动、转移、守卫条件等

学生状态机图

2.6.2 活动图

  • 活动图是状态机的另一种表现形式。用于为一个对象在其生命周期中的行为建模。

  • 活动图是一种描述系统动态行为的图,它用于描述活动的顺序,展现从一个活动到另一个活动的控制流。

喝饮料活动图

2.6.3 状态转移表

  • 状态转移表是展示有限半自动机或有限状态自动机基于当前状态和其他输入,要移动到什么状态(或在非确定有限状态自动机情况下那些状态)的表格。

  • “状态表”本质上是其中某些输入是当前状态,而输出包含与其他输出在一起的下一个状态的真值表。

状态转移表.png

三 状态机在软件领域的应用

3.1 应用场景

  • 正则语言。正则表达式。正则表达式仅仅是用来表示语言规则的一种形式。为了让机器理解正则表达式,我们需要通过程序来实现一种与正则表达式等价的结构,这种结构就是状态机。见《正则表达式DFA构造方法》

例如:[a|b]*abb

正则表达式的NFA
词法分析的基本步骤
  • 网络协议。对于电信行业网络核心软件来说,“有限状态机”思想是基石。如TCP状态机(TCP Finite State Machine
The TCP Finite State Machine (FSM)
  • 游戏设计。复杂的状态、事件、动作。游戏主逻辑、游戏大厅等具有复杂UI交互的类,都可以考虑使用状态机来进行代码编写,细分状态,保证代码的健壮性,方便以后扩展新的特性。例如:挂机时自动刷怪。见《游戏开发之状态机的实现与优化》
角色自动关机状态图

var menu = {

    // 当前状态

    currentState: 'hide',

    // 绑定事件

    initialize: function() {

      var self = this;

      self.on("hover", self.transition);

    },

    // 状态转换

    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 的主要思想是通过构建可复用组件来构建用户界面。所谓组件其实就是React有限状态机,通过状态渲染对应的界面,且每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段进行改变和执行。

// 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,
    };
  }

  /**
   * 点击事件方法 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)}>
            点击{this.state.countnum}次
        </button>
      </div>
    );
  }
}

  • 业务系统。业务系统的本质就是描述真实的世界,所以几乎所有的业务系统里都会有状态机的身影。

例如1:购入流程

购入流程状态图
  • ......

小结:状态机在软件行业使用广泛,它们都有一个共通的特点,将所有的状态、事件、动作都抽离出来,对复杂的状态迁移逻辑统一管理。状态机让复杂的问题变得直观、简单、易懂、解耦、易管理。

3.2 编码中如何运用状态机

引例:空调工作机制简化后的模型,如何编码实现。

  1. 假设遥控器只有两个按钮,power电源键和cool制冷键。

  2. 空调的运行呈现3个状态,停止/Off、仅送风/FanOnly、制冷/Cool。

  3. 起始状态为Off 。

空调工作状态图.png
  • 方法一:if-esle / switch-case 模式

package com.mhc.sample;

import static com.mhc.sample.Aircon.Event.*;
import static com.mhc.sample.Aircon.State.*;

/**
 * 空调
 *
 * @author xiaolong
 * @date 18/6/11 下午5:54
 */
public class Aircon {
    /**
     * 空调当前状态
     */
    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;
    }

    /**
     * 空调状态枚举
     */
    public enum State {
        //关闭中状态
        OFF,
        //送风中状态
        FAN_ONLY,
        //制冷中状态
        COOL
    }

    /**
     * 空调事件枚举
     */
    public enum Event {
        //点击电源键
        CLICK_POWER,
        //点击制冷键
        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;
    }
}

缺点:

a. 当状态很多的时候,维护起来非常麻烦,容易出错。

b. 不容易定位错误,对于状态的理解也不清晰。

c. 这段代码没有实现有限状态机和具体事件动作的隔离。

  • 方法二:状态迁移表法,使用数组与函数引用组合实现

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.*;

/**
 * 空调 - 状态转移表模式
 *
 * @author xiaolong
 * @date 18/6/11 下午5:54
 */
public class AirconTable {
    /**
     * 状态转移表
     */
    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() ));
        }
    };

    /**
     * 空调当前状态
     */
    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;
    }
    /**
     * 转移
     */
    static class Transfor {
        //开始状态
        State startState;
        //事件
        Event event;
        //目标状态
        State nextState;
        //执行动作
        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;
        }
    }

    /**
     * 空调状态枚举
     */
    public enum State {
        //关闭中状态
        OFF,
        //送风中状态
        FAN_ONLY,
        //制冷中状态
        COOL
    }

    /**
     * 空调事件枚举
     */
    public enum Event {
        //点击电源键
        CLICK_POWER,
        //点击制冷键
        CLICK_COOL
    }
}


优点:

a. 状态机可读性比较好

b. 运行时修改状态表非常方便

c. 维护起来简单

d. 可以实现多个状态转换表,根据需要加载不同的转换表。

  • 方法三:状态模式法

状态模式:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。见《状态模式》

  • 环境(Context)角色,也称上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
  • 抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
  • 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。

类图如下:

状态模式类图
  1. 接口实现状态模式
  • Context类

package com.mhc.sample;

/**
 * 状态上下文
 * @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;

/**
 * 状态接口
 * @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;

/**
 * 关闭中状态
 * @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;

/**
 * 送风中状态
 * @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;

/**
 * 制冷中状态
 * @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. 枚举实现状态模式
  • EnumStateContext类

package com.mhc.sample;

/**
 * 状态上下文
 * @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;

/**
 * 枚举实现状态模式
 * @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());
    }
}

优点:

a. 状态维护方便

b. 扩展性强

c. 解耦

  • 方法四:开源框架法

现在状态机开源框架也有不少。

  • squirrel-foundation(702stars,a year ago)
  • spring-statemachine(479stars,2 months ago)
  • stateless4j(349stars,a month ago)

这三款FSMgithub上stars top3的java状态机引擎框架。至于如何技术选型,可参考《状态机引擎选型》。因为spring家族的statemachine活跃度和星级都还不错,所以我果断选择它了。在下一章节我将详细介绍其使用方法,如果想先睹为快,请转到4.4章节Spring State Machine 例子

小结:每种方法各有利弊,具体使用请结合实际场景。

四 Spring State Machine 介绍

4.1 项目概要

该项目自2015年启动,已经3岁啦。

Spring Statemachine(SSM)是基于Spring框架的、实现了状态机概念的框架。 SSM旨在提供以下功能:

  • 简单易用,配置简单

  • 采用层次化状态机结构简化复杂状态配置

  • 类型安全的适配器配置

  • 与Spring Boot、Spring IOC友好集成,bean可以和状态机交互

  • 状态机区域提供更复杂的状态配置

  • 实现了触发器,迁移,警卫,动作行为等概念

  • 提供事件监听器

  • 提供转移拦截器

  • 提供状态机元配置动态化支持

  • 与Spring Security结合提供状态机安全方面的配置

  • 状态机持久化支持Redis、JPA、Mongodb

  • 状态机测试支持

  • 基于ZooKeeper实现的分布式状态机

  • 支持使用UI建模定义状态机配置(Eclipse Papyrus插件)

开源项目模块划分如下:

模块划分.png

4.2 使用场景

以下情况是使用状态机的理想选择:

  • 应用程序结构的一部分可以表示为状态。

  • 你希望复杂的逻辑(如:if-else/swich-case)分成更小的可管理任务。

  • 应用程序已经遭受异步的并发性问题。

如果你准备实现一个状态机:

  • 使用布尔标记和枚举模型的情况

  • 对于某些应用程序生命周期的一部分的有效变量

  • 遍历if-else结构设置特定标示和枚举

4.3 要素和基本概念

Order Shipping
  • State Machine:将状态、转移、事件、动作整合到一起管理的模型。

  • State:一个有限的状态模型,由事件驱动其发生修改。

  • Initial State:状态机启动的特殊状态。初始状态总是绑定到特定的状态机或区域。具有多个区域的状态机可能具有多个初始状态。

  • End State:最终状态是一种特殊的状态,表示封闭区域已完成。如果封闭区域直接包含在状态机中,并且状态机中的所有其他区域也都完成了,则表示整个状态机已完成。

  • History State:一种允许状态机记住其最后活动状态的伪状态。存在两种类型的历史状态,浅层仅记住顶层状态,深层记录子机中的活动状态。

  • Choice State:允许基于事件标题或扩展状态变量进行转换选择的伪状态。

  • Fork State:一种伪状态,可以控制进入某个区域。

  • Join State:一个伪状态,它可以从一个区域提供受控的退出。

  • Extended State:保存在状态机中的一组特殊的变量。

  • Transition:源状态和目标状态之间的关系,由事件驱动其转移。

  • Event:驱动状态发生迁移的事件,可以用枚举或字符串描述。

  • Region:区域是复合状态或状态机的正交部分。它包含状态和转换。

  • Guard:是一个基于扩展状态变量和事件参数值动态计算的布尔表达式。保护条件仅在评估为TRUE时启用操作或转换,并在评估为FALSE时将其禁用,从而影响状态机的行为。

  • Action:动作是在触发转换期间执行的活动行为。

4.4 Spring State Machine 例子

关于状态机如何使用,官网例子有很多,我这里就不细说了,需要先睹简单例子的请移驾官网。

在这里我分享下在生产环境如何优雅的使用状态机?

引例:物流系统订单处理过程。该例子来自本人公司的Jac项目,如果您是内部员工,请移驾gitlab

订单状态机图.png
  • 项目架构
项目架构.png
  • 状态机目录结构,后面会一一说明其功能和实现。
状态机目录.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", "订单状态初始化"),
    RECEIVE_ORDER("RECEIVE_ORDER", "客服接单"),
    SCHEDULE("SCHEDULE", "调度接单"),
    COMPLETE("COMPLETE","完成订单"),
    CANCEL("CANCEL","取消订单"),
    CLOSE("CLOSE","关闭订单"),
    ;

    private String key;
    private String desc;
}

  • 订单状态枚举(OrderState)

package com.mhc.jac.service.core.statemachine.service.state;

//import 省略

/**
 * 订单状态
 *
 * @author xiaolong
 * @Date 18/4/15 下午4:17
 */
@AllArgsConstructor
@Getter
@EnumAnnotation
public enum  OrderState implements BaseEnum {
    WAIT_SUBMIT(0,"WAIT_SUBMIT","待提交(草稿状态)"),
    WAIT_RECEIVING(5,"WAIT_RECEIVING","待接单"),
    WAIT_SCHEDULING(10,"WAIT_SCHEDULING","待调度"),
    PROCESSING(15,"PROCESSING","进行中"),
    COMPLETED(20,"COMPLETED","已完成"),
    CLOSED(99,"CLOSED","已关闭"),
    CANCELED(100,"CANCELED","已取消"),
    ;

    private Integer code;
    private String key;
    private String desc;
}

  • 订单状态机配置(OrderStateMachineConfig)

package com.mhc.jac.service.core.statemachine.service.config.machine;

//import 省略

/**
 * 订单状态机配置
 * @author xiaolong
 * @date 18/4/15 下午4:15
 */
@Configuration
@EnableStateMachineFactory(name="orderStateMachineFactory",contextEvents = false)
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
      //订单行为动作(需要做的业务)
    @Autowired
    private OrderAction orderAction;
    //订单状态机监听器(也可以做相关的业务)
    @Autowired
    private OrderStateMachineListener listener;
    //日志监听器
    @Autowired
    private LogStateMachineListener<OrderState, OrderEvent> logStateMachineListener;
    //状态机运行时持久化配置
    @Autowired
    private StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister;

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config)
            throws Exception {
        config
                .withConfiguration()
                        //注册监听器
                    .listener(listener)
                    .listener(logStateMachineListener)
        ;

        config.withPersistence()
                            //配置运行时持久化对象
                    .runtimePersister(stateMachineRuntimePersister);
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
        states
                .withStates()
                        //初始化订单状态
                    .initial(OrderState.WAIT_SUBMIT)
                    //有限订单状态集合
                    .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)
                    //订单状态初始化事件
                    .event(OrderEvent.INIT_STATE)
                    .and()
                .withExternal()
                    //待接单 -> 待调度
                    .source(OrderState.WAIT_RECEIVING).target(OrderState.WAIT_SCHEDULING)
                    //客服接单事件
                    .event(OrderEvent.RECEIVE_ORDER)
                    //接单业务
                    .action(Actions.withException(orderAction::agentAccept))
                    .and()
                .withExternal()
                    //待接单 -> 已取消
                    .source(OrderState.WAIT_RECEIVING).target(OrderState.CANCELED)
                    //取消订单事件
                    .event(OrderEvent.CANCEL)
                    //取消订单业务
                    .action(Actions.withException(orderAction::cancelOrder))
                    .and()
                .withExternal()
                    //待调度 -> 已取消
                    .source(OrderState.WAIT_SCHEDULING).target(OrderState.CANCELED)
                    //取消订单事件
                    .event(OrderEvent.CANCEL)
                    //取消订单业务
                    .action(orderAction::cancelOrder)
                    .and()
                .withExternal()
                    //待调度 -> 进行中
                    .source(OrderState.WAIT_SCHEDULING).target(OrderState.PROCESSING)
                    //调度接单事件
                    .event(OrderEvent.SCHEDULE)
                    //调度接单业务
                    .action(Actions.withException(orderAction::dispatcherAccept))
                    .and()
                .withExternal()
                    //进行中 -> 已关闭
                    .source(OrderState.PROCESSING).target(OrderState.CLOSED)
                    //关闭订单事件
                    .event(OrderEvent.CLOSE)
                    //关闭订单业务
                    .action(orderAction::closeOrder)
                    .and()
                .withExternal()
                     //进行中 -> 已完成
                    .source(OrderState.PROCESSING).target(OrderState.COMPLETED)
                    //完成订单事件
                    .event(OrderEvent.COMPLETE);
    }
}


  • 订单状态机订阅者(OrderStateMachineSubscriber),这里使用了Guava的事件总线EventBus开源工具将业务与状态机解耦,业务服务发布事件,由状态机来订阅。

package com.mhc.jac.service.core.bus.subscriber.statemachine;
//import 省略

/**
 * 订单状态机订阅者
 *
 * @author xiaolong
 * @date 18/5/22 下午2:02
 */
@Component
@Slf4j
public class OrderStateMachineSubscriber {
     //自定义的状态机服务
    @Autowired
    private CustomStateMachineService<OrderState, OrderEvent> stateMachineService;

    /**
     * 订单业务数据初始化完成时,初始化订单状态
     *
     * @param orderInitFinishEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void initOrderStateMachine(OrderInitFinishEvent orderInitFinishEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, orderInitFinishEvent.getOrderId());

        //发送初始化状态事件
        stateMachine.sendEvent(OrderEvent.INIT_STATE);
    }

    /**
     * 尝试客服接单
     *
     * @param tryAgentAcceptEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryAgentAccept(TryAgentAcceptEvent tryAgentAcceptEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryAgentAcceptEvent.getOrderId());

        //给状态机发送客服接单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.RECEIVE_ORDER)
                .setHeader(ORDER_ID_T_LONG, tryAgentAcceptEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);

    }

    /**
     * 尝试调度接单
     *
     * @param tryDispatcherAcceptEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryDispatcherAccept(TryDispatcherAcceptEvent tryDispatcherAcceptEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryDispatcherAcceptEvent.getOrderId());

        //给状态机发送调度接单事件
        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) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryCancelEvent.getOrderId());

        //给状态机发送取消订单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.CANCEL)
                .setHeader(ORDER_ID_T_LONG, tryCancelEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);
    }

    /**
     * 尝试关闭订单
     *
     * @param tryCloseEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryCloseOrder(TryCloseEvent tryCloseEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryCloseEvent.getOrderId());

        //给状态机发送关闭订单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.CLOSE)
                .setHeader(ORDER_ID_T_LONG, tryCloseEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);
    }
}

  • 订单任务(OrderAction),主要调用订单业务服务处理对应的业务。注意Action中不要直接写业务内容,业务内容由业务服务负责。

package com.mhc.jac.service.core.statemachine.service.action;

//import 省略

/**
 * 订单任务
 * @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);
    }

    /**
     * 调度接单
     * @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);

        //订单取消成功后,发送订单取消任务执行完成事件
        if(orderService.cancelOrder(orderId)){
            CancelCompleteEvent cancelCompleteEvent = CancelCompleteEvent.builder()
                    .orderId(orderId)
                    .build();
            cancelCompleteEvent.setFrom(OperatingEventEnum.ORDER_CANCEL.getDesc());
            cancelCompleteEvent.setSendTime(LocalDateTime.now());

            eventBus.post(cancelCompleteEvent);
        }
    }

    /**
     * 关闭订单
     * @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);
    }
}

  • 订单状态机监听器(OrderStateMachineListener),目前没有写什么业务。

package com.mhc.jac.service.core.statemachine.service.listener;

//import 省略

import java.util.Objects;

/**
 * 订单状态机监听器
 *
 * @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");
    }
}

  • 订单状态机基础配置(OrderStateMachineBaseConfig),主要包括提供状态机类型、订单状态更新方法、运行时持久化配置、日志监听器、订单状态机管理服务。其继承父类(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);
    }
}

- 状态机基础配置,抽象类(StateMachineBaseConfig)


package com.mhc.jac.service.core.statemachine.base.config;

//import 省略

/**
 * 状态机基础配置
 * @Author wangxiaolong <xiaolong@maihaoche.com>
 * @Date 18/4/18 下午1:58
 */
public abstract class StateMachineBaseConfig<S extends Enum<S>, E extends Enum<E>> {

    /**
     * 获取状态机业务类型
     * @return
     */
    protected abstract StateMachineTypeEnum supplierStateMachineType();

    /**
     * 保存业务状态
     * @param context
     */
    protected abstract void saveBizState(CustomStateMachineContext<S,E> context);

    /**
     * 保存业务状态配置
     * @return
     */
    protected BizStatePersistingConfig<S, E> bizStatePersistingConfig() {
        return BizStatePersistingConfig.<S, E>builder()
                .saveBizSate(this::saveBizState)
                .stateMachineType(supplierStateMachineType())
                .build();
    }

    /**
     * 状态机器监听器记录日志
     * @return
     */
    protected LogStateMachineListener<S,E> logStateMachineListener(){
        return new LogStateMachineListener<>();
    }

    /**
     * 状态机运行时持久化
     * @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);
    }

    /**
     * 状态机器交互统一服务
     * @param stateMachineFactory
     * @param stateMachineRuntimePersister
     * @return
     */
    protected CustomStateMachineService<S,E> stateMachineService(
            StateMachineFactory<S,E> stateMachineFactory,
            StateMachineRuntimePersister<S,E,String> stateMachineRuntimePersister) {

        return new CustomStateMachineService<>(stateMachineFactory, stateMachineRuntimePersister);
    }
}

  • 业务状态持久化配置(BizStatePersistingConfig)

package com.mhc.jac.service.core.statemachine.base.config;

//import 省略

/**
 * 业务状态持久化配置
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @Date 18/4/20 下午3:34
 */
@Getter
@Builder
public class BizStatePersistingConfig<S,E> {
    /**
     * 保存业务状态的方法
     */
    private Consumer<CustomStateMachineContext<S,E>> saveBizSate;
    /**
     * 状态机业务类型
     */
    private StateMachineTypeEnum stateMachineType;
}

  • 自定义状态机运行时持久化(CustomStateMachineRuntimePersister

),该类继承父类JpaPersistingStateMachineInterceptor,本质上是状态机拦截器,当状态改变时将状态机上下文和业务状态持久化到数据库。


package com.mhc.jac.service.core.statemachine.base.custom;

//import 省略

/**
 * 自定义状态机运行时持久化
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/20 下午3:20
 */
@Slf4j
public class CustomStateMachineRuntimePersister<S,E,T> extends JpaPersistingStateMachineInterceptor<S,E,T> {
    /**
     * 保存业务状态的配置
     */
    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 {
        //回写业务
        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);
        }

        //回写状态机
        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);
    }
}
  • 自定义状态机上下文(CustomStateMachineContext),包括业务ID和状态机类型。状态机ID是由 "业务类型_业务ID"组成。

package com.mhc.jac.service.core.statemachine.base.custom;

//import 省略

/**
 * 自定义状态机上下文
 * @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;
    }
}
  • 状态机管理服务(CustomStateMachineService),包括状态机的获取和释放。

package com.mhc.jac.service.core.statemachine.base.custom;

//import 省略

/**
 * 状态机管理服务 (包括状态机的获取和释放)
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/20 上午10:43
 */
@Slf4j
public class CustomStateMachineService<S,E> extends DefaultStateMachineService<S,E> {
    /**
     * 状态机本地缓存
     */
    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,"状态机类型不能为空");
        Assert.notNull(bizId,"业务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;
        }
        //从数据库获取内容上下文
        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 异常预警通知等,或消息队列处理
        };
    }

    /**
     * 构建带有异常回执的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,回执异常", stateContext.getException());
                //通过扩展属性回执异常
                stateContext.getExtendedState()
                        .getVariables()
                        .put(KeyConstant.STATE_MACHINE_ACTION_EXCEPTION_T_EXCEPTION,e);

                throw e;
            }
        };

    }
}

- 状态机工具类(StateMachineUtils)


package com.mhc.jac.service.core.statemachine.base;

//import 省略

/**
 * 状态机工具箱
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/5/25 下午10:08
 */
public class StateMachineUtils {
    private StateMachineUtils(){}

    /**
     * 获取扩展状态
     * @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)

/**
 * 状态机任务定义
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/23 下午2:22
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Action {
}

/**
 * 状态机警卫定义
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/23 下午2:22
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Guard {
}

/**
 * 状态机监听器定义
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/23 下午2:22
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Listener {
}

  • 状态机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:状态配置。
  • StateMachineTransitionConfigurer:迁移配置,可以定义状态迁移接受的事件,以及相应的action。
  • StateMachineConfigurationConfigurer:状态机系统配置,包括action执行器(spring statemachine实例可以配置多个event,存储在内部queue中,并通过sync/async executor执行)、listener(事件监听器)等。
  • StateMachineListener:事件监听器(通过Spring的event机制实现),监听stateEntered(进入状态)、stateExited(离开状态)、eventNotAccepted(事件无法响应)、transition(转换)、transitionStarted(转换开始)、transitionEnded(转换结束)、stateMachineStarted(状态机启动)、stateMachineStopped(状态机关闭)、stateMachineError(状态机异常)等事件,借助listener可以追踪状态迁移过程。
  • StateMachineInterceptor:状态拦截器,不同于StateMachineListener被动监听,interceptor拥有可以改变状态变化链的能力,主要在preEvent(事件预处理)、preStateChange(状态变更的前置处理)、postStateChange(状态变更的后置处理)、preTransition(转化的前置处理)、postTransition(转化的后置处理)、stateMachineError(异常处理)等执行点生效,内部的PersistingStateChangeInterceptor(状态持久化)等都是基于这个扩展协议生效的。
  • StateMachine 状态机实例,spring statemachine支持单例、工厂模式两种方式创建,每个statemachine有一个独有的machineId用于标识machine实例;需要注意的是statemachine实例内部存储了当前状态机等上下文相关的属性,因此这个实例不能够被多线程共享。
  • SSM工作机制
SSM工作原理.jpeg
  • SSM状态迁移过程
转态机状态迁移过程.png

4.6 使用状态机基本原则

  • 状态机中(包括Action、Listener、Guard)强烈不建议直接写业务内容,应该直接调用业务服务,具体业务内容由业务服务实现。这样可以实现状态机与业务细节解耦。

  • 基于事件的驱动模型,业务通过消息驱动状态机。

  • 业务主流程脉络由状态机统一管理。

  • 状态机之间不能直接调用,需要通过消息驱动。

4.7 值得思考

  • 状态机异常处理机制如何优雅处理?

  • 事务怎么处理?

  • 状态机粒度如何切分?

五 状态机是一种思维方式

瞧,对于我们日常所用的命令式编程,那些复杂的、冗长的if-else业务,难以维护和扩展,每次业务变更修改代码时总是如履薄冰,为什么会这样呢?

无非几点:

  • 业务状态多

  • if-else 层次多而复杂

  • 业务处理过程复杂

  • 业务相互嵌套,耦合性强

那你是否能从复杂的if-else中进行分析、抽象,抽象出状态事件动作的概念,然后对它们统一管理,包装出一个全新的概念-状态机

从小的角度来说,状态机是一种对象行为建模的工具。使用对象有一个明确并且复杂的生命流(3个以上状态),并且状态变迁存在不同的触发条件和处理行为。

从大的角度来说,这其实是一种全新的编程范式-面向状态机编程。将状态机提升到框架纬度,整个系统是由N台状态机组成,每台状态机订阅着自己感兴趣的事件,管理着自己的状态和行为动作,各司其职。它们之间通过事件相互驱动各自的流转,整个业务就在流转中完成。

从宏观角度来说,整个宇宙就是一台巨大的状态机,人类探索宇宙的奥秘,其实是在探索这台机器的运行机制。万事万物皆是状态机,小到细胞的新陈代谢,大脑中神经元的交互,大到地球的生态圈,风云变幻......

亲,你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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,355评论 6 343
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 21,766评论 1 92
  • 牛骨熬的汤散发着奇异的魅力,诱人的香味布满了整间小店。食客们三三两两,低声讨论着各自的话题,店老板带着西北口音喊出...
    Yatoomi阅读 739评论 0 0
  • 胡班!您的变化我都看到了!您的铁人精神我也见识到了! 想起1.0我对您的陌生,那真是遥不可及的事情,我以为您永远那...
    hard_d724阅读 327评论 3 6