Spring Statemachine
spring statemachine框架的作用在于提供一个项目业务切入的视角,关注点不在于具体的业务数据,而是状态流程。
为什么要用状态机?
增强代码的可维护性, 对于流程复杂易变的场景能大大减轻维护的难度。
业务逻辑与状态流程解耦,避免业务与状态“散弹式”维护。
状态流转越复杂,越能体现状态流转的逻辑清晰,减少的“胶水”代码也越多。
相关概念:
- Transition: 状态转移节点,是组成状态机引擎的核心。
- source/from:现态。
- target/to:次态。
- event/trigger:触发节点从现态转移到次态的动作,这里也可能是一个timer。
- guard/when:状态迁移前的校验,执行于action前。
- action:用于实现当前节点对应的业务逻辑处理。
1. 如何初始化状态机
声明States、Events,定义State、Event、Choice、Action间的关系,定义Action
public enum States {
SI,
S1,
S2,
S3
}
public enum Events {
E1,
E2
}
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
//声明伪状态(初始和终止状态)和choice
states.withStates()
.initial(States.SI)
.choice(States.S1)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
//状态内部推进,即单个状态自旋
.withInternal()
.source(States.SI)
.event(Events.E1)
.action(new SimpleAction())
.and()
//两个状态间推进
.withExternal()
.source(States.SI)
.target(States.S1)
.event(Events.E2)
.action(new TestAction(),new ErrorHandlerAction())
.and()
//分支状态
.withChoice()
.source(States.S1)
//类比为If判断,Guard实现类返回true/false,ErrorHandlerAction()会在Action抛异常时执行
.first(States.S2,new FirstGuard(),new FirstAction(),new ErrorHandlerAction())
.last(State.S3);
}
choice里方法设计比较特殊,first、then、last都具有排他性,first类比if,then类比else if,last类比else
2. 怎么注册任务
状态机存在多种模式,单例模式,即全局共用一个状态机实例;以及工厂模式,即每次获取状态机时创建一个状态机实例,多个状态机使用ID进行区分,工厂模式显然更契合企业级开发的场景。
任务的状态流程由状态机进行管控。由于每个任务都会创建一个状态机实例,这会消耗一定的资源,在任务量较少的情况下不影响,当任务数量较多时可引入对象池(较难设计,容易出现并发问题)。
3. 怎么推进状态
向状态机发送事件(sendEvent(Events e))
-
在action中sendEvent推进状态机实现自驱。(是否算是耦合?)
-
Choice中调用Guard方法,返回true/false判断状态如何变化,guard方法可自己实现;为目标state定义action,处理业务逻辑,建议抽象出实现类,将action做为粘连。
使用TimerTrigger(官方用于Internal transition)
- timer(1000):每1000ms执行一次,搭配action使用
- timerOnce(1000):进入目标状态1000ms后执行一次,搭配action使用
4. 怎么读取当前状态
spring状态机引擎设计了StateContext来传递状态机当前状态的上下文,其中包括的信息有:
- 当前的
Message
或Event
(或它们的MessageHeaders
,如果已知的话)。 - 状态机的
Extended State
。 StateMachine
本身。- 可能的状态机错误。
- 当前
Transition
- 状态机的源状态。
- 状态机的目标状态。
- 当前的
Stage
,stage表示状态机与用户的交互状态详情,如:EVENT_NOT_ACCEPTED、STATE_CHANGED、STATE_ENTRY等
5. 状态机持久化
- 持久化StateMachineContext(简称状态机上下文)到redis或db,通过获取状态机上下文重建状态机(因为对象图太多,普通Java序列化无法保存,官方推荐默认多张表进行存储)
- 伪持久化,仅持久化任务状态和所需数据,通过build新状态机并设置状态及数据来实现重建状态机的目的(数据量小)
6. 业务逻辑异常怎么处理
spring-statemachine的异常处理比较独特,它会捕获异常并转为警告,状态也能成功转移到次态,调用业务逻辑后需要手动判断(或者使用拦截器)是否发生异常,通常以变量的方式来传递信息,存储在StateContext中
7. 监听器和拦截器
官方已经定义好了listener和interceptor的接口方法。
- listener运行监听上下文事件(对应上面的Stage,如OnStateExitEvent、OnStateEntryEvent、OnStateChangedEvent),在发生各种状态更改、操作等时获得回调。
- interceptor可以拦截和停止当前状态的更改或更改其状态转换逻辑,例如上面提到的异常处理拦截器,官方也提供了相应的示例,Spring Statemachine - 参考文档
存在问题:
3.0.x后引入了reactor响应式编程,不建议使用原有的同步方法,sendEvent方法参数变为流,后续升级及维护可能需要引入reactor
长等待action(如等待用户确认)需要长时间轮询,这个过程状态机和线程都会被占用,可否将任务关闭,等待用户操作后重建状态机;但是这样引入了未被状态机管控的流程,不符合我们管理状态流程的初衷
参考文献
Alan宇 的个人主页 - 文章 - 掘金 (juejin.cn)