Spring状态机

状态机的定义、选型和quick start

什么是状态机

状态机就是状态模式的一种实现,状态模式 (State Pattern)是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

状态机的全称是有限状态自动机(FSM,有限状态机wiki),有多种类型的动作:

  • 进入动作(entry action):在进入状态时进行
  • 退出动作(exit action):在退出状态时进行
  • 输入动作:依赖于当前状态和输入条件进行
  • 转移动作:在进行特定转移时进行

它可以使用如下图的状态转移图来表示,也可以用状态转移表来表示 Alt text

在状态机中有四个主要概念:

  1. state(状态): 一个状态机至少包含两个状态,如上图中的opened、closed两个状态
  2. event(事件): 就是触发状态变化的一个扳机,所以有些地方也叫trigger
  3. action(动作): 事件发生后需要执行的动态
  4. transition(转换过程): 从一个状态转化到另一个状态的全部过程

状态机选型

  • stateless4j(github地址):stateless(c#)的Java版本
    • 优点: 轻量、简单易用、性能良好
    • 缺点: 功能有限,无法满足复杂场景(如不支持state持久化、缺少扩展点等)
  • Squirrel(github地址):
    • 优点: 功能丰富,易于扩展
    • 缺点: 有一定学习成本
  • Spring Statemachine(github地址):
    • 优点: Spring生态,功能丰富,有丰富的扩展点
    • 缺点: StateMachine实例创建比较重

Spring状态机Quick Start

github demo地址: https://github.com/RussXia/spring-state-machine-sample

订单状态:

public enum OrderStatusEnum {
    UNPAID(1, "待支付"),
    WAITING_DELIVERY(2, "待发货"),
    WAITING_FOR_RECEIVE(3, "待收货"),
    RECEIVED(4, "已签收"),
    REJECTED(5, "已拒收"),
}

订单事件

public enum OrderEventEnum {
    PAY(1, "支付"),
    DELIVERY(2, "发货"),
    RECEIVE(3, "收货"),
    REJECT(4, "拒收"),
}

状态机配置:

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEventEnum> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(OrderStatusEnum.UNPAID).target(OrderStatusEnum.WAITING_DELIVERY)
                .event(OrderEventEnum.PAY).action(simpleAction, errorAction)
                .and()
                .withExternal()
                .source(OrderStatusEnum.WAITING_DELIVERY).target(OrderStatusEnum.WAITING_FOR_RECEIVE)
                .event(OrderEventEnum.DELIVERY).action(simpleAction, errorAction)
                .and()
                .withExternal()
                .source(OrderStatusEnum.WAITING_FOR_RECEIVE).target(OrderStatusEnum.RECEIVED)
                .event(OrderEventEnum.RECEIVE).action(simpleAction, errorAction)
                .and()
                .withExternal()
                .source(OrderStatusEnum.WAITING_FOR_RECEIVE).target(OrderStatusEnum.REJECTED)
                .event(OrderEventEnum.REJECT).action(rejectGoodsAction, errorAction);
    }