SpringBoot系列—透彻理解「状态机」的全套机制 (附完整源码)

Posted helloworld2016425

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot系列—透彻理解「状态机」的全套机制 (附完整源码)相关的知识,希望对你有一定的参考价值。

1. 消息传递

状态机可以看成是一个控制中心,接受外部的事件信号进行状态转移,而状态转移也是在状态机初始化的时候就设置好了的。但实际业务中,我们不仅仅只是需要控制中心进行状态转移,还会需要进行一些业务的处理。

状态机机制中的事件处理器执行相关业务逻辑,就会需要获得业务的数据,这时候触发事件时候就需要传递业务数据到处理器中,正好spring的状态机类statemachine提供了传递事件消息的api。如下图:

技术图片

/**
     * Send an event {@code E} wrapped with a {@link Message} to the region.
     *
     * @param event the wrapped event to send
     * @return true if event was accepted
     */
    boolean sendEvent(Message<E> event);

    /**
     * Send an event {@code E} to the region.
     *
     * @param event the event to send
     * @return true if event was accepted
     */
    boolean sendEvent(E event);

 

Message实现类

//非完整代码,因代码太多,只截取部分展示
public class GenericMessage<T> implements Message<T>, Serializable {

    private static final long serialVersionUID = 4268801052358035098L;


    private final T payload;//这个保存这事件枚举对象OrderEvents

    private final MessageHeaders headers;//Map类型,保存多个参数对象
......

 

private final T payload;//这个保存这事件枚举对象OrderEvents

private final MessageHeaders headers;//Map类型,保存多个参数对象

创建的事件消息类Message保存事件对象和参数对象,通过以下方式发送到状态机。

  //用message传递数据
Order order = new Order(orderId, "547568678", "广东省深圳市", "13435465465", "RECEIVE");
Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).setHeader("otherObj", "otherObjValue").build();
stateMachine.sendEvent(message);

 

RECEIVE事件被触发,事件处理器被触发打印如下结果:

消息: Order [id=null, userId=547568678, address=广东省深圳市, phoneNum=13435465465, state=RECEIVE]

 

RECEIVE事件对应的处理器方法:

    /**
     * WAITING_FOR_RECEIVE->DONE 执行的动作
     */
    @OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
    public void receive(Message<OrderEvents> message) {
        logger.info("---【WithStateMachine】用户已收货,订单完成---");
        Object object = message.getHeaders().get("order");
        Order order = null;
        if(!ObjectUtils.isEmpty(object))
        {
            order = (Order)object;
            System.out.println("消息: "+order.toString());
        }else {
            System.out.println("消息为空");
        }
    }

 

处理器类使用@WithStateMachine(name="orderSingleMachine")修饰,name属性关联着状态机。事件处理器方法使用@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")修饰,表明现态和次态。

小结:这样就实现了状态机业务层controller到状态机处理器的业务数据传递,以消息的形式传递。

2. 事件监听

状态机创建,使用@EnableStateMachine注解修饰继承EnumStateMachineConfigurerAdapter适配类的子类。而状态机的监听器就是在这个子类中注入的。

    //一定要注意这里设置监听器,那么该监听器就只能作用于 此状态机,状态机工厂 创建的状态机无法创建
    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        // TODO Auto-generated method stub
        config.withConfiguration().listener(listener());
    }

 

创建监听器创建对象

default  StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<OrderStates, OrderEvents>() {

            @Override
            public void transition(Transition<OrderStates, OrderEvents> transition) {
                if(transition.getTarget().getId() == OrderStates.UNPAID) {
                    logger.info("【StateMachineListener】订单创建,待支付");
                    return;
                }

                if(transition.getSource().getId() == OrderStates.UNPAID
                        && transition.getTarget().getId() == OrderStates.WAITING_FOR_RECEIVE) {
                    logger.info("【StateMachineListener】用户完成支付,待收货");
                    return;
                }

                if(transition.getSource().getId() == OrderStates.WAITING_FOR_RECEIVE
                        && transition.getTarget().getId() == OrderStates.DONE) {
                    logger.info("【StateMachineListener】用户已收货,订单完成");
                }

                
                if(transition.getSource().getId() == OrderStates.WAITING_FOR_RECEIVE
                        && transition.getTarget().getId() == OrderStates.WAITING_FOR_GOODSBACK) {
                    logger.info("【StateMachineListener】用户已退货,待商家收货");
                }
                
                if(transition.getSource().getId() == OrderStates.WAITING_FOR_GOODSBACK
                        && transition.getTarget().getId() == OrderStates.DONE) {
                    logger.info("【StateMachineListener】商家已退款,订单完成");
                }
                
                if(transition.getTarget().getId() == OrderStates.DONE) {
                    logger.info("【StateMachineListener】订单结束");
                }
            }

        };
    }
    
}

 

注意:这种监听器配置后,作用的只能是这个@EnableStateMachine注解创建的状态机配置类创建的状态机。跟spring的状态机工厂配置监听器是同样的方式,不过修饰的注解是@EnableStateMachineFactory,具体实现请看本项目源码,这里不再赘述。

监听器效果:

最终状态:DONE
otherOrderStateFactoryMachine
【OrderFactoryOtherEventConfig】---订单创建,待支付---
【StateMachineListener】订单创建,待支付
 started 
【OrderFactoryOtherEventConfig】---用户完成支付,待收货---
【StateMachineListener】用户完成支付,待收货
【OrderFactoryOtherEventConfig】---用户已收货,订单完成---
【StateMachineListener】用户已收货,订单完成
【StateMachineListener】订单结束
最终状态:DONE

 

从结果可知:

  • 监听器总是比事件处理器后执行。
  • 监听器只能监听状态变更,无法获取事件参数,处理器可以

3. 多个以及多种状态机并存

  1. 在项目中@EnableStateMachine(name="orderSingleMachine")以spring提供的方式创建了一个状态机对象

  2. StateMachineConfig类中配置了状态枚举数据与初态,状态转移图谱与事件,监听器

  3. 在OrderSingleEventConfig类里配置了处理器的逻辑

  4. 启动项目第一次调用 http://localhost/statemachine/testSingleOrderState,结果如下:

---【WithStateMachine】订单创建,待支付---
【StateMachineListener】订单创建,待支付
 ---【WithStateMachine】用户完成支付,待收货---
【StateMachineListener】用户完成支付,待收货
---【WithStateMachine】用户已收货,订单完成---
消息为空
【StateMachineListener】用户已收货,订单完成
【StateMachineListener】订单结束
最终状态:DONE

 

多次调用显示如下:

最终状态:DONE
最终状态:DONE
最终状态:DONE
最终状态:DONE
最终状态:DONE
最终状态:DONE

 

因此:这种方式创建的状态机只能用一次。

这时候就需要用到状态机工厂来多次创建状态机。

好在工厂方式跟上面方式一样,只有注解不同。工厂方式使用@EnableStateMachineFactory。使用工厂方式可以多次创建状态机对象,这样就解决了上面一直是最终状态的问题。工厂方式和普通方式的区别:

  • 使用的注解不一样,工厂方式:@EnableStateMachineFactory,普通方式:@EnableStateMachine。

  • 工厂方式创建的状态机name需要在创建的时候指定,普通方式的name就是注解的name属性,如下代码:

orderMachineFactory.getStateMachine(StateMachineFactoryConfig.orderStateMachineId);

 

4. 状态分支

前面提到的都是简单的状态转移,有些时候我们需要做一些条件判断,根据条件结果再做状态转移。而这种条件判断的状态都是短暂停留等待条件结果,然后再跳转到下一个状态。

这跟前面提到的一个状态跳转到两个次态对应两个事件情况是不一样的,这里的情况是分支场景,而statemachine用withChoice方法实现了分支,具体实现可以看项目源码的复杂表单状态机实现。

配置状态图谱代码:

.withChoice()
.source(ComplexFormStates.CHECK_CHOICE)
.first(ComplexFormStates.CONFIRM_FORM, new ComplexFormCheckChoiceGuard(),new ComplexFormChoiceAction())
.last(ComplexFormStates.DEAL_FORM,new ComplexFormChoiceAction())

 

first方法表示条件判断为true是的状态转移,last方法表示条件为false时的状态转移。

ComplexFormCheckChoiceGuard类是条件判断类,ComplexFormChoiceAction类是状态转移到CONFIRM_FORM后执行的动作。

具体测试样例请查看我的项目源码。

5. 状态机工厂

状态机工厂创建状态机在本项目中有两种方式:

  • 一种是前面介绍的使用@EnableStateMachineFactory注解

  • 使用spring的StateMachineBuilder状态机构造器,用工厂类包装,具体实现是FormStateMachineBuilder和ComplexFormStateMachineBuilder。

 

 

欢迎加入技术群:获得更多java,springboot,redis,kafka圈的好友,共图技术未来

点击获取技术群二维码 

 

想要更多状态机机制实现详情,请关注公众号“前沿科技bot“,发送"状态机2"获取完整项目源码。

技术图片

 

以上是关于SpringBoot系列—透彻理解「状态机」的全套机制 (附完整源码)的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java虚拟机-常用vm参数分析

深入理解JVM虚拟机15:Java线上故障排查全套路总结

终于理解Spring Boot 为什么青睐HikariCP了,图解的太透彻了!

终于理解Spring Boot 为什么青睐HikariCP了,图解的太透彻了!

终于理解Spring Boot 为什么青睐HikariCP了,图解的太透彻了!

深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析