状态机-Spring Statemachine

132 阅读4分钟

参考

  1. 开源中国:spring statemachine 的企业可用级开发指南 8 - 复杂状态机的实现,choice,guard 和 action
    作者:wphmoon
  2. CSDN文章系列:spring statemachine
    作者:wphmoon123
  3. 稀土掘金:彻底搞懂Spring状态机原理,实现订单与物流解耦
    作者:Tom弹架构
  4. 稀土掘金:当转转严选订单遇到状态机
    作者:转转技术团队
  5. 代码先锋网:使用Spring StateMachine框架实现状态机

什么是状态机?

状态机,全名为确定性有穷状态自动机,也常被简称为有穷自动机,简写FSM。状态机维护一组状态集合,和事件集合,能够对特定的事件输入,作出状态流转,并执行相应的动作。

image.png

怎么用Spring实现状态机?

举个发票状态流转简单例子,自己想的,非实际业务场景。

引入依赖

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

关键要素梳理

  1. 状态集合(states),所有状态的枚举
  2. 事件集合(events),触发状态变化的事件枚举
  3. 检测器(guards),也就判断是否满足条件
  4. 转换器(transitions),指定从某状态到某状态
  5. 上下文(context)

画状态流转图

发票状态机2.png

状态枚举

/**
 * 发票状态枚举
 * @date 2022/10/13
 */
@Getter
@AllArgsConstructor
public enum InvoiceStateEnum {
    /** 未开票 */
    NOT_INVOICE(1, "未开票"),
    /** 待开票 */
    WAIT_INVOICE(2, "待开票"),
    /** 开票中 */
    INVOICING(3, "开票中"),
    /** 已开票 */
    INVOICE_SUCCESS(4, "已开票"),
    /** 开票失败 */
    INVOICE_FAIL(5, "开票失败"),
    /** 开票前校验 */
    INVOICE_PRE_CHECK(6, "开票前校验"),
    /** 开票结果校验 */
    INVOICE_RESULT_CHECK(7, "开票结果校验");
    private Integer code;
    private String description;

    /**
     * 根据编码获取枚举
     * @date 2022/10/17
     * @param code 编码
     * @return com.sc.cloud.designpattern.invoicestatemachine.enums.InvoiceState
     */
    public static InvoiceStateEnum valueOf(Integer code) {
        return Arrays.stream(values()).filter(v -> v.code.equals(code)).findFirst().orElse(null);
    }
}

事件枚举

/**
 * 发票事件
 *@date 2022/10/13
 */
@Getter
@AllArgsConstructor
public enum InvoiceEventEnum {
    /** 创建 */
    INIT(1, "创建"),
    /** 触发待开 */
    WAIT_INVOICE(2, "触发待开"),
    /** 取消发票 */
    CANCEL_INVOICE(3, "取消发票"),
    /** 修改发票信息 */
    CHANGE_INVOICE(4, "修改发票信息"),
    /** 开票前校验 */
    CHECK_INVOICE_PRE(5, "开票前校验"),
    /** 开票结果校验 */
    CHECK_INVOICE_RESULT(6, "开票结果校验");
    private Integer code;
    private String description;
}

发票主体对象

/**
 * 发票实体
 *@date 2022/10/17
 */
@Data
public class Invoice {
    private String invoiceTitle;
    private String buyer;
    private Integer state;
}

发票状态机builder

/**
 * 发票状态配置
 *@date 2022/10/13
 */
@Component
@Slf4j
@EnableStateMachine(name = InvoiceStateMachineConstant.MACHINE_ID)
public class InvoiceStateMachineBuilder {
    private final static String MACHINE_ID = "invoiceMachine";

    @Autowired
    private InvoiceStateMachineListener invoiceStateMachineListener;

    public StateMachine<InvoiceStateEnum, InvoiceEventEnum> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<InvoiceStateEnum, InvoiceEventEnum> builder = StateMachineBuilder.builder();
        log.info("构建发票状态机");

        builder.configureConfiguration()
                .withConfiguration()
                .machineId(InvoiceStateMachineConstant.MACHINE_ID)
                .listener(invoiceStateMachineListener)
                .beanFactory(beanFactory);

        builder.configureStates()
                .withStates()
                .initial(InvoiceStateEnum.NOT_INVOICE)
                .choice(InvoiceStateEnum.INVOICE_PRE_CHECK)
                .choice(InvoiceStateEnum.INVOICE_RESULT_CHECK)
                .states(EnumSet.allOf(InvoiceStateEnum.class));

        builder.configureTransitions()
                // 未开票 -> 待开票
                .withExternal().source(InvoiceStateEnum.NOT_INVOICE).target(InvoiceStateEnum.WAIT_INVOICE)
                .event(InvoiceEventEnum.WAIT_INVOICE)
                // action 可以传入两个
                .action(new InvoiceWaitInvoiceAction())
                .guard(new InvoicePreCheckGuard())
                .and()
                // 待开票 -> 未开票
                .withExternal().source(InvoiceStateEnum.WAIT_INVOICE).target(InvoiceStateEnum.NOT_INVOICE)
                .event(InvoiceEventEnum.CANCEL_INVOICE)
                .action(new InvoiceCancelInvoiceAction())
                .and()
                // 待开票 -> 开票前校验
                .withExternal().source(InvoiceStateEnum.WAIT_INVOICE).target(InvoiceStateEnum.INVOICE_PRE_CHECK)
                .event(InvoiceEventEnum.CHECK_INVOICE_PRE)
                .and()
                // 开票前校验 -> 开票中 / 开票失败
                .withChoice().source(InvoiceStateEnum.INVOICE_PRE_CHECK)
                .first(InvoiceStateEnum.INVOICING, new InvoicePreCheckGuard())
                .last(InvoiceStateEnum.INVOICE_FAIL)
                .and()
                // 开票中 -> 开票结果校验
                .withExternal().source(InvoiceStateEnum.INVOICING).target(InvoiceStateEnum.INVOICE_RESULT_CHECK)
                .event(InvoiceEventEnum.CHECK_INVOICE_RESULT)
                .and()
                // 开票失败 -> 待开票
                .withExternal().source(InvoiceStateEnum.INVOICE_FAIL).target(InvoiceStateEnum.WAIT_INVOICE)
                .event(InvoiceEventEnum.CHANGE_INVOICE)
                .and()
                // 开票结果校验 -> 开票成功 / 开票失败
                .withChoice().source(InvoiceStateEnum.INVOICE_RESULT_CHECK)
                .first(InvoiceStateEnum.INVOICE_SUCCESS, new InvoiceResultCheckGuard())
                .last(InvoiceStateEnum.INVOICE_FAIL);

        return builder.build();
    }

}

事件处理Action

/**
 *@date 2022/10/14
 */
@Slf4j
public class InvoiceWaitInvoiceAction implements Action<InvoiceStateEnum, InvoiceEventEnum> {
    @Override
    public void execute(StateContext<InvoiceStateEnum, InvoiceEventEnum> stateContext) {
        log.info("创建发票,保存发票信息...{}", JSON.toJSONString(stateContext));
    }
}
/**
 *@date 2022/10/14
 */
@Slf4j
public class InvoiceCancelInvoiceAction implements Action<InvoiceStateEnum, InvoiceEventEnum> {
    @Override
    public void execute(StateContext<InvoiceStateEnum, InvoiceEventEnum> stateContext) {
        log.info("取消开票...参数:{}", JSON.toJSONString(stateContext));
    }
}

状态机转换器配置(@OnTransition)

/**
 * 状态转化器,和listener实现同样的效果,这种方式更优雅,但listener有更多的设置
 *@date 2022/10/13
 */
@WithStateMachine(id = "invoiceMachine")
@Slf4j
public class InvoiceEventConfig {

    @OnTransition(target = "NOT_INVOICE")
    public void create() {
        log.info("发票创建事件...");
    }

    @OnTransition(source = "NOT_INVOICE", target = "WAIT_INVOICE")
    public void waitInvoice(Message<InvoiceEventEnum> message) {
        log.info("发票待开事件参数:{}", JSON.toJSONString(message));
        log.info("发票待开事件...");
    }
}

判断状态变化条件guard

/**
 * 校验是否满足开票前判断
 *@date 2022/10/13
 */
@Slf4j
public class InvoicePreCheckGuard implements Guard<InvoiceStateEnum, InvoiceEventEnum> {
    @Override
    public boolean evaluate(StateContext<InvoiceStateEnum, InvoiceEventEnum> stateContext) {
        log.info("开票前进行校验...");
        return true;
    }
}
/**
 * 校验是否满足开票结果判断
 *@date 2022/10/13
 */
@Slf4j
public class InvoiceResultCheckGuard implements Guard<InvoiceStateEnum, InvoiceEventEnum> {
    @Override
    public boolean evaluate(StateContext<InvoiceStateEnum, InvoiceEventEnum> stateContext) {
        log.info("开票结果校验...");
        return false;
    }
}

状态监听器Listener

/**
 *@date 2022/10/17
 */
@Component
@Slf4j
public class InvoiceStateMachineListener implements StateMachineListener<InvoiceStateEnum, InvoiceEventEnum> {
    @Override
    public void stateChanged(State<InvoiceStateEnum, InvoiceEventEnum> state, State<InvoiceStateEnum, InvoiceEventEnum> state1) {
        // 注意初始化状态没赋值 源状态state是null
        log.info("更新到状态:{}", state1.getId());
    }

    @Override
    public void stateEntered(State<InvoiceStateEnum, InvoiceEventEnum> state) {
        log.info("执行stateEntered");
    }

    @Override
    public void stateExited(State<InvoiceStateEnum, InvoiceEventEnum> state) {
        log.info("执行stateExited");
    }

    @Override
    public void eventNotAccepted(Message<InvoiceEventEnum> message) {
        // 仅在状态不满足转化打印,guard判断不满足不会输出
        log.info("不满足状态转化条...");
    }

    @Override
    public void transition(Transition<InvoiceStateEnum, InvoiceEventEnum> transition) {
        log.info("执行transition");
    }

    @Override
    public void transitionStarted(Transition<InvoiceStateEnum, InvoiceEventEnum> transition) {
        log.info("执行transitionStarted");
    }

    @Override
    public void transitionEnded(Transition<InvoiceStateEnum, InvoiceEventEnum> transition) {
        log.info("执行transitionEnded");
    }

    @Override
    public void stateMachineStarted(StateMachine<InvoiceStateEnum, InvoiceEventEnum> stateMachine) {
        log.info("执行stateMachineStarted");
    }

    @Override
    public void stateMachineStopped(StateMachine<InvoiceStateEnum, InvoiceEventEnum> stateMachine) {
        log.info("执行stateMachineStopped");
    }

    @Override
    public void stateMachineError(StateMachine<InvoiceStateEnum, InvoiceEventEnum> stateMachine, Exception e) {
        log.info("状态机发生异常:{}", e.getMessage(), e);
    }

    @Override
    public void extendedStateChanged(Object o, Object o1) {
        log.info("执行extendedStateChanged");
    }

    @Override
    public void stateContext(StateContext<InvoiceStateEnum, InvoiceEventEnum> stateContext) {
        log.info("执行stateContext");
    }
}

发票状态持久器Persist

/**
 * 发票状态机持久化(伪持久化)
 *@date 2022/10/17
 */
@Component
public class InvoiceStateMachinePersist implements StateMachinePersist<InvoiceStateEnum, InvoiceEventEnum, Invoice> {
    @Override
    public void write(StateMachineContext<InvoiceStateEnum, InvoiceEventEnum> stateMachineContext, Invoice invoice) throws Exception {
        // 这里不做任何持久化工作,实际使用中只要从数据库取出状态加载到状态机中即可
    }

    @Override
    public StateMachineContext<InvoiceStateEnum, InvoiceEventEnum> read(Invoice invoice) throws Exception {
        StateMachineContext<InvoiceStateEnum, InvoiceEventEnum> result = new DefaultStateMachineContext<>(
                InvoiceStateEnum.valueOf(invoice.getState()), null, null, null, null, InvoiceStateMachineConstant.MACHINE_ID
        );
        return result;
    }
}

持久器配置

/**
 *@date 2022/10/17
 */
@Configuration
public class PersistConfig {
    @Autowired
    private InvoiceStateMachinePersist invoiceStateMachinePersist;

    @Bean(name = "invoicePersist")
    public StateMachinePersister<InvoiceStateEnum, InvoiceEventEnum, Invoice> invoicePersister() {
        return new DefaultStateMachinePersister<>(invoiceStateMachinePersist);
    }
}

测试

@Autowired
private InvoiceStateMachineBuilder invoiceStateMachineBuilder;

@Resource(name = "invoicePersist")
private StateMachinePersister<InvoiceStateEnum, InvoiceEventEnum, Invoice> persister;

@Autowired
private BeanFactory beanFactory;

@Test
public void builder() throws Exception {
    StateMachine<InvoiceStateEnum, InvoiceEventEnum> stateMachine = invoiceStateMachineBuilder.build(beanFactory);
    System.out.println(stateMachine.getId());

    stateMachine.start();

    Invoice invoice = new Invoice();
    invoice.setInvoiceTitle("抬头");
    invoice.setBuyer("购方姓名");

    Message<InvoiceEventEnum> message = MessageBuilder.withPayload(InvoiceEventEnum.WAIT_INVOICE).setHeader("invoice", invoice).build();
    stateMachine.sendEvent(message);
    System.out.println("当前状态:" + stateMachine.getState().getId());

    // 恢复状态
    invoice.setState(4);
    persister.restore(stateMachine, invoice);
    // 查看恢复后状态机的状态
    System.err.println("恢复后的状态:" + stateMachine.getState().getId());
}