Implementation Principle and Mechanism of Spring Finite State Machine

1. Concept

Spring Statemachine is a framework for application developers to use the state machine concept in Spring applications. From the design level analysis: the purpose of the state machine is to solve the complex state management process, to ensure the single principle of the program and the principle of opening and closing; business perspective analysis: state machine It should be driven by initialization state, loading all existing states, events, transitions, actions, and triggering the next state, and solve the strong coupling between business logic and state management.

2. Spring Statemachine aims to provide the following functions:

  • An easy-to-use flat one-level state machine for simple use cases.
  • Hierarchical state machine structure to simplify complex state configuration.
  • The state machine area provides more complex state configuration.
  • Use of triggers, transitions, guards and actions.
  • Type-safe configuration adapter.
  • Builder pattern for easy instantiation outside of a Spring application context
  • Recipes for Common Use Cases
  • Zookeeper-based distributed state machine
  • State machine event listener.
  • UML Eclipse Papyrus Modeling.
  • Store machine configuration in persistent storage.
  • Spring IOC integration associates beans with state machines.

State machines are powerful because the behavior is always guaranteed to be consistent, making debugging relatively easy. This is because the operating rules are immutable at machine startup. The idea is that your application may exist in a finite number of states, and certain predefined triggers can take your application from one state to the next. Such triggers can be event or timer based.

It's much easier to define high-level logic outside of the application and then rely on a state machine to manage the state. You can interact with the state machine by sending events, listening for changes, or simply requesting the current state.

3. State machine application scenarios

Introduce spring-statemachine dependency before use:
 <dependency>
     <groupId>org.springframework.statemachine</groupId>
     <artifactId>spring-statemachine-core</artifactId>
     <version>2.1.2.RELEASE</version>
 </dependency>
Define the drive state:
public enum OrderStatus {
    
    
    WAIT_PAYMENT, // 待付款
    WAIT_DELIVER, // 待发货
    WAIT_RECEIVE, // 待收货
    FINISH, // 已收货
    WAIT_COMMENT, // 待评论
    COMMENTED, // 已评论
    UNCOMMENTED; // 未评论
}
Define driver events:
public enum ChangeEvent {
    
    
    PAYED,  // 支付
    DELIVERY, // 发货
    RECEIVED, // 收货
    COMMENT; // 评价
}
Drive transitions through the states and events defined above:
@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);
    }
}
Interpretation before and after execution of relevant method calls:
  • withExternal is written when the source and target are different, such as the state changes after the payment is successful.
  • withInternal The serial writing method when the source and target are the same, for example, after the payment fails, it is still in the pending payment state.
  • The source and target of withExternal are used for the state before and after the execution, the event is the triggered event, and the guard judges whether to execute the action. Execute the final action after satisfying the conditions of source, target, event, and guard at the same time.
  • withChoice When performing an action that may lead to multiple results, you can choose to use choice+guard to jump
  • withChoice executes the logic of first/then according to the judgment result of guard.
  • withChoice doesn't need to send an event to trigger.
State transition driver listener:
@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());
    }
}
Start the service state machine (initialize all driver states):
@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());
    }
}

4. Summary on the use of StateMachine

  • Define state machine related states and event-driven enumerations;
  • All states used by the state machine and state management initialization;
  • Drive state transition event mechanism (including state transition, event, action);
  • Implements specified listeners for state machine driven transition action events.

Guess you like

Origin blog.csdn.net/weixin_43322048/article/details/119951984