状态机选型
- squirrel-foundation
- Spring statemachine
- stateless4j
关于这几个对比,文章比较多,这里就不赘述了。
这三个,我基本都有直接或间接使用过。
简单感受是:
squirrel-foundation,同事用过,功能丰富,较为轻量,上手简单。
Spring statemachine,自己调研过,和 Spring 天然集成,注解声明,功能丰富,同时上手较复杂,状态机实例不能单例使用,线程不安全。
stateless4j ,轻量、简单,轻度使用,很 nice,对代码侵入极小。GitHub - stateless4j/stateless4j: Lightweight Java State Machine
实践
质量检测任务(简称:质检任务)的状态流转图:
new 一个类,名为:QcStateMachine 质检状态机。
依据上面的状态转移图,配置状态转移。
提前准备两个枚举类:状态枚举、时间枚举。前置态-接受到事件->后置态。
private static final StateMachineConfig<QualityStatusEnum, QualityEventEnum> CONFIG = new StateMachineConfig<>();
static {
/*
* 最初为 INIT 状态时
*/
CONFIG.configure(QualityStatusEnum.INIT)
// .permitDynamic(new TriggerWithParameters1<>(QualityEventEnum.APPLY_QUALITY_TASK, QcTask.class), qcTask -> {
// return QualityStatusEnum.CHECKING;
// })
.permit(QualityEventEnum.APPLY_QUALITY_TASK, QualityStatusEnum.CHECKING)
.permit(QualityEventEnum.SAVE_QUALITY_RESULT, QualityStatusEnum.CHECKING)
.permit(QualityEventEnum.SUBMIT_QUALITY_RESULT, QualityStatusEnum.RECHECKING)
.ignore(QualityEventEnum.DELETE);
CONFIG.configure(QualityStatusEnum.CHECKING)
.ignore(QualityEventEnum.SAVE_QUALITY_RESULT)
.permit(QualityEventEnum.SUBMIT_QUALITY_RESULT, QualityStatusEnum.RECHECKING);
CONFIG.configure(QualityStatusEnum.RECHECKING)
.ignore(QualityEventEnum.SUBMIT_RECHECK_RESULT)
.permit(QualityEventEnum.RECHECK_EXPIRE, QualityStatusEnum.RECHECKED);
CONFIG.configure(QualityStatusEnum.RECHECKED)
.permit(QualityEventEnum.AMEND, QualityStatusEnum.AMENDED)
.permit(QualityEventEnum.AMEND_EXPIRE, QualityStatusEnum.AMENDED);
}
//构建状态机实例,传入当前状态和状态转移配置
StateMachine<QualityStatusEnum, QualityEventEnum> sm = new StateMachine<>(current, CONFIG)
//判断当前状态能不能接受目标事件: 提交质检结果
if (sm.canFire(QualityEventEnum.SUBMIT_QUALITY_RESULT)) {
// 中断
}
//接受事件,状态转移到下一个状态
sm.fire();
//更新表里的状态
QualityStatusEnum nextState = sm.getState(); //推荐这样获取下一个状态,减少硬编码
...
上面时最简单的轻量使用。
如果需要在状态转移时,伴随一些动作逻辑,可以在状态转移配置中设置。
QcStateMachine 完整代码:
import com.github.oxo42.stateless4j.StateMachine;
import com.github.oxo42.stateless4j.StateMachineConfig;
import com.sankuai.groceryrisk.common.core.exception.DialogException;
import com.sankuai.groceryrisk.risk.investigation.service.constant.enums.QualityEventEnum;
import com.sankuai.groceryrisk.risk.investigation.service.constant.enums.QualityStatusEnum;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
*
* @author L&J
* @version 0.1
* @since 2023/2/4 12:10
*/
@Slf4j
public class QcStateMachine {
private static final StateMachineConfig<QualityStatusEnum, QualityEventEnum> CONFIG = new StateMachineConfig<>();
static {
/*
* 最初为 INIT 状态时
*/
CONFIG.configure(QualityStatusEnum.INIT)
// .permitDynamic(new TriggerWithParameters1<>(QualityEventEnum.APPLY_QUALITY_TASK, QcTask.class), qcTask -> {
// return QualityStatusEnum.CHECKING;
// })
.permit(QualityEventEnum.APPLY_QUALITY_TASK, QualityStatusEnum.CHECKING)
.permit(QualityEventEnum.SAVE_QUALITY_RESULT, QualityStatusEnum.CHECKING)
.permit(QualityEventEnum.SUBMIT_QUALITY_RESULT, QualityStatusEnum.RECHECKING)
.ignore(QualityEventEnum.DELETE);
CONFIG.configure(QualityStatusEnum.CHECKING)
.ignore(QualityEventEnum.SAVE_QUALITY_RESULT)
.permit(QualityEventEnum.SUBMIT_QUALITY_RESULT, QualityStatusEnum.RECHECKING);
CONFIG.configure(QualityStatusEnum.RECHECKING)
.ignore(QualityEventEnum.SUBMIT_RECHECK_RESULT)
.permit(QualityEventEnum.RECHECK_EXPIRE, QualityStatusEnum.RECHECKED);
CONFIG.configure(QualityStatusEnum.RECHECKED)
.permit(QualityEventEnum.AMEND, QualityStatusEnum.AMENDED)
.permit(QualityEventEnum.AMEND_EXPIRE, QualityStatusEnum.AMENDED);
}
public static StateMachine<QualityStatusEnum, QualityEventEnum> of(Integer sourceState) {
return of(QualityStatusEnum.getByValue(sourceState));
}
public static StateMachine<QualityStatusEnum, QualityEventEnum> of(QualityStatusEnum sourceState) {
return new StateMachine<>(sourceState, CONFIG);
}
public static boolean canFire(Integer current, QualityEventEnum event) {
return canFire(QualityStatusEnum.getByValue(current), event);
}
/**
* 当前状态是否接受当前事件
* @param current 当前状态, 前置态
* @param event 事件
*/
public static boolean canFire(QualityStatusEnum current, QualityEventEnum event) {
StateMachine<QualityStatusEnum, QualityEventEnum> sm = new StateMachine<>(current, CONFIG);
return sm.canFire(event);
}
public static StateMachine<QualityStatusEnum, QualityEventEnum> checkFireOrThrow(Integer current, QualityEventEnum event) {
return checkFireOrThrow(QualityStatusEnum.getByValue(current), event);
}
public static StateMachine<QualityStatusEnum, QualityEventEnum> checkFireOrThrow(QualityStatusEnum current, QualityEventEnum event) {
StateMachine<QualityStatusEnum, QualityEventEnum> sm = new StateMachine<>(current, CONFIG);
if (!sm.canFire(event)) {
throw new DialogException("当前任务状态('{}')不可'{}'", current.getLabel(), event.getLabel());
}
return sm;
}