• Spring Statement 状态机应用实例


    在业务系统中,通过应用状态机的方式,将所有的状态、事件、动作都抽离出来,对复杂的状态迁移逻辑进行统一管理,来取代冗长的 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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();
            
            // 业务逻辑+数据持久化
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    异常处理

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    实际应用

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    根据当前状态获取对应的状态机
    @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;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    接口调用
    @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    异常处理
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    全局异常处理器
    @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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    可优化点

    1、将实例池化,避免每个请求实例化状态机,节省开销
    2、使用持久化状态机StateMachinePersister恢复状态机对象
    3、使用唯一id记录每个状态机


    参考资料:

    1. Spring Statement
    2. 有限状态机及Spring Statement框架
  • 相关阅读:
    华为云云耀云服务器L实例评测|云耀云服务器L实例部署DjangoBlog个人博客系统
    C#泛型的逆变协变(个人理解)
    【数据结构】树与二叉树(十八):树的存储结构——Father链接结构、儿子链表链接结构
    九、Redis事务锁机制、连接池
    STM32F103ZE单片机呼吸灯源代码
    你的第一款开源视频分析框架
    JuiceFS 元数据引擎选型指南
    MAC电脑连接外接显示屏,颜色显示有问题,又粉、紫色蒙版,问题处理(1)
    代码随想录训练营第41天|LeetCode 343. 整数拆分、 96.不同的二叉搜索树
    【C语言】数据结构的基本概念与评价算法的指标
  • 原文地址:https://blog.csdn.net/why_still_confused/article/details/127703607