一、概念

Spring Statemachine 是应用程序开发人员在 Spring 应用程序中使用状态机概念的框架,从设计层面分析:状态机目的是解决复杂的状态管理流程,保证程序单一原则和开闭原则;业务角度分析:状态机应有初始化状态、加载所有已有的状态、事件、转移、动作、触发下一个状态为驱动,并解决了业务逻辑与状态管理之间的强耦合。

二、 Spring Statemachine 旨在提供以下功能:

  • 易于使用的平面一级状态机,适用于简单的用例。
  • 分层状态机结构以简化复杂的状态配置。
  • 状态机区域提供更复杂的状态配置。
  • 触发器、转换、守卫和动作的使用。
  • 类型安全配置适配器。
  • 用于在 Spring 应用程序上下文之外轻松实例化的构建器模式
  • 通常用例的食谱
  • 基于 Zookeeper 的分布式状态机
  • 状态机事件侦听器。
  • UML Eclipse Papyrus 建模。
  • 将机器配置存储在持久存储中。
  • Spring IOC 集成将 bean 与状态机相关联。

状态机很强大,因为行为总是被保证一致,使得调试相对容易。这是因为操作规则是在机器启动时一成不变的。这个想法是您的应用程序可能存在于有限数量的状态中,并且某些预定义的触发器可以将您的应用程序从一个状态带到下一个状态。此类触发器可以基于事件或计时器。

在应用程序之外定义高级逻辑然后依靠状态机来管理状态要容易得多。您可以通过发送事件、监听更改或简单地请求当前状态来与状态机交互。

三、状态机应用场景

使用之前引入spring-statemachine依赖:
<dependency>
     <groupId>org.springframework.statemachine</groupId>
     <artifactId>spring-statemachine-core</artifactId>
     <version>2.1.2.RELEASE</version>
 </dependency>
定义驱动状态:
public enum OrderStatus {
    WAIT_PAYMENT, // 待付款
    WAIT_DELIVER, // 待发货
    WAIT_RECEIVE, // 待收货
    FINISH, // 已收货
    WAIT_COMMENT, // 待评论
    COMMENTED, // 已评论
    UNCOMMENTED; // 未评论
}
定义驱动事件:
public enum ChangeEvent {
    PAYED,  // 支付
    DELIVERY, // 发货
    RECEIVED, // 收货
    COMMENT; // 评价
}
通过上面定义的状态和事件实现驱动转移:
@Configuration
@EnableStateMachine
@Slf4j
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, ChangeEvent> {
    
    @Resource
    private OrderStateListenerImpl orderStateListener;

    @Resource
    private PayedAction payedAction;

    @Resource
    private DeliveryAction deliveryAction;

    @Resource
    private ReceivedAction receivedAction;

    @Resource
    private CommentedAction commentedAction;

    @Resource
    private UncommentedAction uncommentedAction;

    @Resource
    private DeliveryGuard deliveryGuard;

    @Resource
    private PayedGuard payedGuard;

    @Resource
    private ReceivedGuard receivedGuard;

    @Resource
    private CommentGuard commentGuard;


    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, ChangeEvent> states) throws Exception {
        states.withStates()
              // 设置初始化状态
              .initial(OrderStatus.WAIT_PAYMENT)
              // 设置用于条件判断的状态
              .choice(OrderStatus.FINISH)
              // 绑定全部状态
              .states(EnumSet.allOf(OrderStatus.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, ChangeEvent> transitions) throws Exception {
        // 1、withExternal 是当source和target不同时的写法
        // 2、withInternal 当source和target相同时的串联写法,比如付款失败时,付款前及付款后都是待付款状态
        // 3、withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转
        transitions
                // 通过PAYED 实现由 WAIT_PAYMENT => WAIT_DELIVER 状态转移
                .withExternal()
                    .source(OrderStatus.WAIT_PAYMENT)
                    .target(OrderStatus.WAIT_DELIVER)
                    .event(ChangeEvent.PAYED)
                    .guard(payedGuard)
                    .action(payedAction)
                .and()
                // 通过DELIVERY 实现由 WAIT_DELIVER => WAIT_RECEIVE 状态转移
                .withExternal()
                    .source(OrderStatus.WAIT_DELIVER)
                    .target(OrderStatus.WAIT_RECEIVE)
                    .event(ChangeEvent.DELIVERY)
                    .guard(deliveryGuard)
                    .action(deliveryAction)
                .and()
                // 通过RECEIVED 实现由 WAIT_RECEIVE => FINISH 状态转移
                .withExternal()
                    .source(OrderStatus.WAIT_RECEIVE)
                    .target(OrderStatus.FINISH)
                    .event(ChangeEvent.RECEIVED)
                    .guard(receivedGuard)
                    .action(receivedAction)
                .and()
                // Choice的状态选择,
                // commentGuard的结果为true则执行first
                // commentGuard的结果为true则执行then
                .withChoice()
                    .source(OrderStatus.FINISH)
                    .first(OrderStatus.COMMENTED, commentGuard, commentedAction)
                    .then(OrderStatus.UNCOMMENTED, commentGuard, uncommentedAction)
                    .last(OrderStatus.WAIT_COMMENT);
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStatus, ChangeEvent> config) throws Exception {
        // 状态转移监听事件
        config.withConfiguration().listener(orderStateListener);
    }
}
相关方法调用执行前后解释:
  • withExternal 是当source和target不同时的写法,比如付款成功后状态发生的变化。
  • withInternal 当source和target相同时的串联写法,比如付款失败后都是待付款状态。
  • withExternal的source和target用于执行前后状态、event为触发的事件、guard判断是否执行action。同时满足source、target、event、guard的条件后执行最后的action。
  • withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转
  • withChoice根据guard的判断结果执行first/then的逻辑。
  • withChoice不需要发送事件来进行触发。
状态转移驱动监听器:
@Component
@Slf4j
public class OrderStateListenerImpl extends StateMachineListenerAdapter<OrderStatus, ChangeEvent> {
    
    @Override
    public void stateChanged(State<OrderStatus, ChangeEvent> from, State<OrderStatus, ChangeEvent> to) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("stateChanged");
        stringBuilder.append(" from " + (null != from ? from.getId().name() : null));
        stringBuilder.append(" to " + (null != to ? to.getId().name() : null));
        log.info(stringBuilder.toString());
    }

    @Override
    public void transition(Transition<OrderStatus, ChangeEvent> transition) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("transition");
        stringBuilder.append(" kind " + (null != transition.getKind() ? transition.getKind().name() : null));
        stringBuilder.append(" from " + (null != transition.getSource() ? transition.getSource().getId().name() : null));
        stringBuilder.append(" to " + (null != transition.getTarget() ? transition.getTarget().getId().name() : null));
        stringBuilder.append(" trigger " + (null != transition.getTrigger() ? transition.getTrigger().getEvent().name() : null));
        log.info(stringBuilder.toString());
    }
}
启动服务状态机(初始化所有驱动状态):
@SpringBootApplication
@Slf4j
public class FsmApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(FsmApplication.class, args);
    }

    @Resource
    private StateMachine<OrderStatus, ChangeEvent> stateMachine;

    @Override
    public void run(String... args) throws Exception {
        stateMachine.start();
        // 测试状态机消息变更
        messageTransfer();
        stateMachine.stop();
    }

    private void messageTransfer() {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOid(110350339917373440L);
        orderInfo.setDesc("test order");
        Message<ChangeEvent> message = null;
        log.info("current state {}", stateMachine.getState().getId().name());
        // spring message的payload设置为消息事件、header为额外需要带的参数
        message = MessageBuilder.withPayload(ChangeEvent.PAYED).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        log.info("current state {}", stateMachine.getState().getId().name());
        message = MessageBuilder.withPayload(ChangeEvent.DELIVERY).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        log.info("current state {}", stateMachine.getState().getId().name());
        message = MessageBuilder.withPayload(ChangeEvent.RECEIVED).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        log.info("current state {}", stateMachine.getState().getId().name());
    }
}

四、关于StateMachine使用总结

  • 定义状态机相关状态和事件驱动枚举;
  • 状态机使用的所有状态以及状态管理初始化;
  • 驱动状态转移事件机制(包含状态转移、事件、动作);
  • 为状态机驱动转移动作事件实现指定监听器。