前言
有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移 和动作等行为的数学模型。应用FSM模型可以帮助对象生命周期的状态的顺序以及导致状态变化的事件进行管理。将状态和 事件控制从不同的业务方法的分支中抽离出来。FSM的应用范围很广,对于有复杂状态流,扩展性要求比较高的场景都可以 使用该模型。后续围绕一个简单订单流程进行展开深入解剖状态机引擎在生产开发中使用。
技术选型
关于状态机引擎的选型,其实目前市面上有不少框架,github上按照statemachine关键字搜索可以出来好多结果。 考虑到资料完备情况、与项目的集成容易程度、框架是否尚在维护等条件,这里选择spring statemachine 看spring的产品,首先看其quickstart项目,然后根据reference做字典式学习,
- 官网:http://projects.spring.io/spring-statemachine
- 对应的reference:https://docs.spring.io/spring-statemachine/docs/2.2.0.RELEASE/reference
- 以及对应的源码&samples:https://github.com/spring-projects/spring-statemachine
- 集成方便,对于使用Java语言的应用来说,可以选择的集成框架也比较多。如squirrel-foundation、spring-statemachine、stateless4j。squirrel-foundation、stateless4j相对spring-statemachine 更加轻量级。感兴趣的可以去看下这两个项目的源码。
集成依赖
-
在Spring boot技术栈中集成比较简单,添加starter依赖即可 在maven构建中,添加依赖如下:
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-starter</artifactId> <version>最新版本号</version> </dependency>
在gradle构建中,添加依赖如下:
compile('org.springframework.statemachine:spring-statemachine-starter:最新版本号')
-
同样在Spring技术栈中集成也比较简单,添加core依赖即可 在maven构建中,添加依赖如下:
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>最新版本号</version> </dependency> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-data-common</artifactId> <version>最新版本号</version> </dependency>
在gradle构建中,添加依赖
compile 'org.springframework.statemachine:spring-statemachine-core:最新版本号'
compile 'org.springframework.statemachine:spring-statemachine-data-common:最新版本号'
基本概念
四要素
状态机模型中的4个要素,即现态、条件、动作、次态:
- 现态:是指当前所处的状态。
- 条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
- 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的, 当条件满足后,也可以不执行任何动作,直接迁移到新状态。
- 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
状态机定义
- StateMachine:状态机模型
- state:S-状态,一般定义为一个枚举类,如创建、待风控审核、待支付等状态
- event:E-事件,同样定义成一个枚举类,如订单创建、订单审核、支付等,代表一个动作。 一个状态机的定义就由这两个主要的元素组成,状态及对对应的事件(动作)。
状态机相关概念
- Transition: 节点,是组成状态机引擎的核心;
- source:节点的当前状态;
- target:节点的目标状态;
- event:触发节点从当前状态到目标状态的动作;
- guard:起校验功能,一般用于校验是否可以执行后续action;
- action:用于实现当前节点对应的业务逻辑处理;
e.g
builder.configureTransitions() -- 配置节点
.withExternal() //表示source target两种状态不同
.source(CREATE) //当前节点状态
.target(WYD_INITIAL_JUMP) //目标节点状态,这里是设置了个中间状态
.event(BizOrderStatusChangeEventEnum.EVT_CREATE) //导致当前变化的动作/事件
.action(orderCreateAction, errorHandlerAction) //执行当前状态变更导致的业务逻辑处理,以及出异常时的处理
备注:
- withExternal 是当source和target不同时的写法,如上例子
- withInternal 当source和target相同时的串联写法,比如付款失败时,付款前及付款后都是待付款状态
- withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转目前主要使用这三种, 当然如果有更复杂的情况,还有withLocal以及withJunction 等各种写法。 当你对一项技术并不是那么了解时,先尽可能的用简单的实现来处理你的业务,出现问题的可能性最小, 后续可以再慢慢扩展。
注意:
-
上述代码event之后并没有设置guard,代表默认会执行对应action。
-
这里的orderCreateAction及errorHandlerAction都是使用spring注入进来的业务bean。
稍微复杂点的节点配置
接上面的withExternal,下面使用choice做了一个分支选择,如下:
.and() // 使用and串联
.withChoice() // 使用choice来做选择
.source(WYD_INITIAL_JUMP) // 当前状态
.first(WAIT_REAL_NAME_AUTH, needNameAuthGurad(), needNameAuthAction) // 第一个分支
.last(WAIT_BORROW, waitBorrowAction) // 第二个分支
说明:
- 多个节点之间使用and()串联
- 注意,withChoice没有对应的event,所以依赖上一个节点的event,只要source是WYD_INITIAL_JUMP, 就会自动执行当前choice transition
- withChoice没有对应的target,只有几个选择分支
- first/then/last 类似于
if--else if--else
,then
可以不设置,但是last一定要有,否则会报错 needNameAuthGurad() 用于判断是否走当前分支,返回true时选择当前分支,返回false走下面分支 - last中其实是省略了一个guard的实现,默认为true,意味着如果不走first分支,就会走last分支。
- withChoice其实是个PseudoState,也就是伪状态或瞬时状态,会马上跳转到下面的分支流程中。
在实际开发中如果是复杂一点的流程,比如有子状态,那么就有一个region的概念,region-区域,表示有一系列状态, 这些状态有相同的父状态
来源:oschina
链接:https://my.oschina.net/VILLE/blog/4306163