Spring Statemachine 状态机初探

/ Java / 没有评论 / 1721浏览

Spring Statemachine 状态机初探

开篇

有限状态机

Spring Statemachine

定义状态

public enum OrderStatus {
    WAIT_PAYMENT, // 待付款
    WAIT_DELIVER, // 待发货
    WAIT_RECEIVE, // 待收货
    FINISH, // 已收货
    WAIT_COMMENT, // 待评论
    COMMENTED, // 已评论
    UNCOMMENTED; // 未评论
}

定义事件

public enum ChangeEvent {
    PAYED,  // 支付
    DELIVERY, // 发货
    RECEIVED, // 收货
    COMMENT; // 评价
}

状态转移

@Configuration
@EnableStateMachine(name = "stateMachine")
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, ChangeEvent> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @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);
    }
}

状态转移监听器

@Component
public class OrderStateListenerImpl extends StateMachineListenerAdapter<OrderStatus, ChangeEvent> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @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));
        logger.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));

        logger.info(stringBuilder.toString());
    }
}

启动服务

@SpringBootApplication
public class FsmApplication implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(getClass());

    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(123456789L);
        orderInfo.setDesc("test order");
        Message<ChangeEvent> message = null;
        logger.info("current state {}", stateMachine.getState().getId().name());
        // spring message 的payload设置为消息事件、header为额外需要带的参数
        message = MessageBuilder.withPayload(ChangeEvent.PAYED).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        logger.info("current state {}", stateMachine.getState().getId().name());
        message = MessageBuilder.withPayload(ChangeEvent.DELIVERY).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        logger.info("current state {}", stateMachine.getState().getId().name());
        message = MessageBuilder.withPayload(ChangeEvent.RECEIVED).setHeader("order", orderInfo).build();
        stateMachine.sendEvent(message);
        logger.info("current state {}", stateMachine.getState().getId().name());
    }
}

##状态转移过程输出

transition kind INITIAL from null to WAIT_PAYMENT trigger null
stateChanged from null to WAIT_PAYMENT
current state WAIT_PAYMENT

PayedGuard payLoad "PAYED" messageHeader{"desc":"test order","oid":123456789}
PayedAction execute
transition kind EXTERNAL from WAIT_PAYMENT to WAIT_DELIVER trigger PAYED
stateChanged from WAIT_PAYMENT to WAIT_DELIVER
current state WAIT_DELIVER

DeliveryGuard payLoad "DELIVERY" messageHeader{"desc":"test order","oid":123456789}
DeliveryAction execute
transition kind EXTERNAL from WAIT_DELIVER to WAIT_RECEIVE trigger DELIVERY
stateChanged from WAIT_DELIVER to WAIT_RECEIVE
current state WAIT_RECEIVE

PayedGuard payLoad "RECEIVED" messageHeader{"desc":"test order","oid":123456789}
ReceivedAction execute
transition kind EXTERNAL from WAIT_RECEIVE to FINISH trigger RECEIVED


CommentGuard payLoad "RECEIVED" messageHeader{"desc":"test order","oid":123456789} flag 1
CommentGuard payLoad "RECEIVED" messageHeader{"desc":"test order","oid":123456789} flag 2
UncommentedAction execute
stateChanged from WAIT_RECEIVE to UNCOMMENTED
current state UNCOMMENTED

参考