在业务系统中,通过应用状态机的方式,将所有的状态、事件、动作都抽离出来,对复杂的状态迁移逻辑进行统一管理,来取代冗长的 if else 判断,能使系统中的复杂问题得以解耦,变得直观、方便操作,使系统更加易于维护和管理。
有限状态机的定义及重要概念见有限状态机
Spring Statement 应用实例
Spring Statemachine 旨在提供以下功能:
- 易于使用的扁平单级状态机,可用于简单案例
- 分层状态机结构,以简化复杂的状态配置
- 状态机区域提供更复杂的状态配置
- 可以使用触发器、转换流程、守卫和动作
- 类型安全配置适配器
- 构建器模式,用于在
Spring Application
上下文之外使用的简易实例化 - 常用用例的手册
- 基于
Zookeeper
的分布式状态机 - 状态机事件监听器
UML Eclipse Papyrus
建模- 状态机配置的持久化存储
Spring IOC
集成将bean
与状态机相关联
Spring状态机框架的重要概念可参考Spring Statemachine,以下就不重复概念,直接展示实例
State
流程状态
@Getter
@AllArgsConstructor
public enum ProcessState {
/**
* 申请状态(100为中间状态,状态机自动流转,不持久化)
*/
NONE(-1, "无"),
DATA_SUBMIT(0, "资料审核进行中"),
DATA_AUDIT(1, "资料审核进行中"),
DATA_AUDIT_FAIL(2, "资料审核不通过"),
GUILD_ELECTRONIC_SIGN(3, "公会签约"),
ANCHOR_ELECTRONIC_SIGN(4, "主播签约"),
ELECTRONIC_SIGN_FAIL(5, "签约不通过"),
PROCESS_SUCCESS(6, "流程成功"),
FINAL_AUDIT_FAIL(7, "终审不通过"),
DATA_AUDIT_SUCCESS(100, "资料审核通过");
private final int value;
private final String detail;
public static ProcessState get(int value) {
for (ProcessState s : values()) {
if (value == s.value) {
return s;
}
}
return NONE;
}
}
Event
触发事件
public enum ProcessEvent {
/**
* 申请状态 0不通过 1通过 2拒绝
*/
FAIL(0, "不通过"),
SUCCESS(1, "通过"),
REJECT(2, "拒绝");
private final int value;
private final String desc;
public static ProcessEvent get(int value) {
for (Events s : values()) {
if (value == s.value) {
return s;
}
}
return FAIL;
}
}
Action
行为动作
@Service
public class DataAuditAction implements Action<States, Events> {
@Override
public void execute(StateContext<States, Events> context) {
log.info("DataAuditAction, state:{}, msg:{}", context.getStateMachine().getState().getId(),
context.getMessage().toString());
// 传递数据
MessageHeaders headers = context.getMessage().getHeaders();
Apply apply = headers.get("apply", Apply.class);
// 结果状态
States states = context.getTarget().getId();
// 业务逻辑+数据持久化
}
}
Guard & Choice
在下述状态机中,审核成功后会到审核成功的中间状态,状态机根据withChoice
自动流转状态,满足signGuard
条件则执行guildAction
,否则执行anchorAction
@Service
public class SignGuard implements Guard<ProcessState, ProcessEvent> {
@Override
public boolean evaluate(StateContext<ProcessState, ProcessEvent> context) {
Apply apply = context.getMessage().getHeaders().get("apply", Apply.class);
return apply.getType() != null && apply.getType() == 0;
}
}
builder.configureStates()
.withStates()
.initial(ProcessState.DATA_SUBMIT)
.choice(ProcessState.DATA_AUDIT_SUCCESS)
.states(EnumSet.allOf(ProcessState.class));
builder.configureTransitions()
// 资料提交
.withExternal()
.source(ProcessState.DATA_SUBMIT).target(ProcessState.DATA_AUDIT)
.event(ProcessEvent.SUCCESS)
.action(submitAction, errorHandlerAction)
.and()
// 审核通过
.withExternal()
.source(States.DATA_AUDIT).target(States.DATA_AUDIT_SUCCESS)
.event(Events.SUCCESS)
.and()
.withChoice()
.source(ProcessState.DATA_AUDIT_SUCCESS)
.first(ProcessState.GUILD_ELECTRONIC_SIGN, signGuard, guildAction, errorHandlerAction)
.last(ProcessState.ANCHOR_ELECTRONIC_SIGN, anchorAction, errorHandlerAction);
异常处理
ErrorAction
@Service
public class ErrorHandlerAction implements Action<State, Event> {
@Override
public void execute(StateContext<State, Event> context) {
Exception ex = context.getException();
Map<Object, Object> var = context.getExtendedState().getVariables();
var.put("hasError", true);
var.put("error", ex);
var.put("msg", ex.getMessage());
}
}
实际应用
StateMachine构造器
@Component
@EnableStateMachine
public class ProcessStateMachineBuilder {
private final static String MACHINE_ID = "ProcessMachine";
@Resource(name = "errorHandlerAction")
private ErrorHandlerAction errorHandlerAction;
@Resource(name = "guildAction")
private GuildAction guildAction;
@Resource(name = "signGuard")
private SignGuard signGuard;
/**
* 构建状态机
*
* @return StateMachine
* @throws Exception
*/
public StateMachine<ProcessState, ProcessEvent> build() throws Exception {
StateMachineBuilder.Builder<ProcessState, ProcessEvent> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.machineId(MACHINE_ID);
builder.configureStates()
.withStates()
.initial(ProcessState.DATA_SUBMIT)
.choice(ProcessState.DATA_AUDIT_SUCCESS)
.states(EnumSet.allOf(ProcessState.class));
builder.configureTransitions()
// 资料提交
.withExternal()
.source(ProcessState.DATA_SUBMIT).target(ProcessState.DATA_AUDIT)
.event(ProcessEvent.SUCCESS)
.action(submitAction, errorHandlerAction)
.and()
// 审核通过
.withExternal()
.source(ProcessState.DATA_AUDIT).target(States.DATA_AUDIT_SUCCESS)
.event(ProcessEvent.SUCCESS)
.and()
.withChoice()
.source(ProcessState.DATA_AUDIT_SUCCESS)
.first(ProcessState.GUILD_ELECTRONIC_SIGN, signGuard, guildAction, errorHandlerAction)
.last(ProcessState.ANCHOR_ELECTRONIC_SIGN, anchorAction, errorHandlerAction)
.and()
// 签约失败
.withExternal()
.source(ProcessState.ANCHOR_ELECTRONIC_SIGN)
.target(ProcessState.ELECTRONIC_SIGN_FAIL)
.event(ProcessEvent.FAIL)
.action(auditFailAction, errorHandlerAction);
return builder.build();
}
}
根据当前状态获取对应的状态机
@Service
public class StateMachineCommonService {
private static final Logger logger = LoggerFactory.getLogger(StateMachineCommonService.class);
@Autowired
private ProcessStateMachineBuilder builder;
/**
* 根据保底考核状态获取对应的状态机
*
* @param state 状态 {@link ProcessState}
* @return StateMachine
*/
public StateMachine<ProcessState, ProcessEvent> getStateMachineByState(ProcessState state) {
try {
StateMachine<ProcessState, ProcessEvent> stateMachine = builder.build();
stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.resetStateMachine(new DefaultStateMachineContext<>
(state, null, null, null, null, stateMachine.getId())));
stateMachine.start();
logger.info("getStateMachineByStateCode, state:{}", stateMachine.getState().getId().toString());
return stateMachine;
} catch (Exception e) {
logger.error("getStateMachineByStateCode error, state:{}", state, e);
return null;
}
}
}
接口调用
@Transactional(rollbackFor = {BusinessException.class, Exception.class})
public void dataAudit(AuditCommReq req) throws Exception {
Apply apply = applyRepository.queryById(req.getId());
StateMachine<ProcessState, ProcessEvent> stateMachine = stateMachineCommonService
.getStateMachineByState(ProcessState.DATA_AUDIT);
Message<ProcessEvent> message = MessageBuilder.withPayload(ProcessEvent.SUCCESS)
.setHeader("apply", apply)
.build();
stateMachine.sendEvent(message);
StateMachineUtils.throwError(stateMachine);
}
异常处理
public class StateMachineUtils {
public static <S, E> boolean throwError(StateMachine<S, E> stateMachine) throws Exception {
Map<Object, Object> var = stateMachine.getExtendedState().getVariables();
Boolean flag = (Boolean) var.get(StateMachineConstants.HAS_ERROR);
if (flag != null && flag) {
throw (Exception) var.get(StateMachineConstants.ERROR);
}
return false;
}
}
全局异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
@ResponseBody
public PageDataResult businessWarn(BusinessException exception) {
logger.warn("businessWarn error: passport={} msg={}", LocalUserContext.get().getPassport(), exception.getMessage(), exception);
return PageDataResult.fail(RespCode.BUSINESS_WARN.getCode(), exception.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseBody
public PageDataResult commonException(Exception exception) {
logger.error("commonException error: passport={} msg={}", LocalUserContext.get().getPassport(), exception.getMessage(), exception);
return PageDataResult.of(RespCode.ERROR);
}
}
可优化点
1、将实例池化,避免每个请求实例化状态机,节省开销
2、使用持久化状态机StateMachinePersister
恢复状态机对象
3、使用唯一id记录每个状态机
参考资料: