美文网首页
spring statemachine-传递参数的message

spring statemachine-传递参数的message

作者: 张晨辉Allen | 来源:发表于2020-04-30 14:48 被阅读0次

    1.传递参数的message

       在企业开发中,数据在不同的业务间传输是最常见的工作,所以虽然我们的主架构是用的状态机,也就是从流程状态的角度来看待这个项目,但在具体业务中,每个状态的转变中会牵涉到各类业务,这些业务有些需要收到状态机变化的通知,需要把状态值传递给业务类和业务方法,同样的,在处理状态变化是,也需要获取业务数据,方便不同的业务在同一个状态变化环节做各自的业务,下面我们就讲下这个数据在spring statemachine里面的传递。

       这次我们的顺序变一下,由外部传入一个订单号到controller开始:

    @RequestMapping("/testOrderState")
        public void testOrderState(String orderId) throws Exception {
    
            StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
            System.out.println(stateMachine.getId());
    
            // 创建流程
            stateMachine.start();
    
            // 触发PAY事件
            stateMachine.sendEvent(OrderEvents.PAY);
    
            // 触发RECEIVE事件
            Order order = new Order(orderId, "547568678", "广东省深圳市", "13435465465", "RECEIVE");
            Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).build();
            stateMachine.sendEvent(message);
    
            // 获取最终状态
            System.out.println("最终状态:" + stateMachine.getState().getId());
        }
    

       controller收到request请求的参数,orderId,然后状态机依次触发事件,到触发RECEIVE事件的时候,我们新建了一个Order,并把orderId塞进去了,其实更多的情况应该是我们拿到orderId,然后查询数据库,得到order数据对象,这里为了简化代码,就新建一个啦。
       然后就是真正的主角登场了,Message。它其实不是spirng statemachine专属的,它是spring里面通用的一种消息工具,看它的源代码:

    package org.springframework.messaging;
    
    public interface Message<T> {
    
        /**
         * Return the message payload.
         */
        T getPayload();
    
        /**
         * Return message headers for the message (never {@code null} but may be empty).
         */
        MessageHeaders getHeaders();
    
    }
    

    它由两个部分组成,看图就知道了,和代码里面是一致的

    image

       在spring statemachine里面,我们把状态塞到message的payload里面,然后把需要传递的业务数据(例子里面就是order对象)塞到header里面。创建message用的是messagebuilder,看它的名字就知道是专门创建message的。

    Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).build();
            stateMachine.sendEvent(message);
    

       创建了message后,状态机sendEvent就可以不只是传一个event,可以组合event(OrderEvents.RECEIVE)和数据内容(order)一起发送给状态机变化的处理类eventconfig了。让我们看eventConfig的处理:

    /**
         * WAITING_FOR_RECEIVE->DONE 执行的动作
         */
        @OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
        public void receive(Message<OrderEvents> message) {
            System.out.println("传递的参数:" + message.getHeaders().get("order"));
            logger.info("---用户已收货,订单完成---");
        }
    

    首先,receive方法的参数由之前的为空:

    public void receive() {
            logger.info("---用户已收货,订单完成---");
    }
    

       改成了Message<OrderEvents> message,这样就能从message的getHeaders里面取到传递过来的数据对象了。

       另外如果我们需要传递多个数据对象怎么办呢,比如我们在实际业务中,除了传订单数据,可能还需要把商品数据,或者支付结果数据也传过来,那么也容易,我们还是从controller里面开始:

    Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).setHeader("otherobj", "otherobjvalue").build();
    

    在后面继续setHeader就好了,然后到eventConfig里面:

    System.out.println("传递的参数:" + message.getHeaders().get("order"));
    System.out.println("传递的参数:" + message.getHeaders().get("otherObj"));
    

    运行后看日志:

    传递的参数:Order [id=null, userId=547568678, address=广东省深圳市, phoneNum=13435465465, state=RECEIVE]
    传递的参数:otherObjValue
    

       可知两个的数据都传递到了eventConfig里面了,这个就实现了多个数据对象的同时传递。
       到这里为止,状态机通过message对象就和其他的业务代码做到了数据连接。其实这个很关键,只有做到和其他业务的数据传递,才能算的上真正的可用。

    2.持久化

       目前为止,我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。但在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付环节需要等待一个剁手的用户隔天再下单,所以这里面涉及到一个创建的状态机该何去何从的问题。在spring statemachine中,给出来的办法就是保存起来,到需要的时候取出来用。

    1、持久化到本地内存

    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.statemachine.StateMachineContext;
    import org.springframework.statemachine.StateMachinePersist;
    import org.springframework.stereotype.Component;
    
    /**
     * 在内存中持久化状态机
     */
    @Component
    public class InMemoryStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, String> {
    
        private Map<String, StateMachineContext<OrderStates, OrderEvents>> map = new HashMap<String, StateMachineContext<OrderStates,OrderEvents>>();
        
        @Override
        public void write(StateMachineContext<OrderStates, OrderEvents> context, String contextObj) throws Exception {
            map.put(contextObj, context);
        }
    
        @Override
        public StateMachineContext<OrderStates, OrderEvents> read(String contextObj) throws Exception {
            return map.get(contextObj);
        }
    
    }
    

    这个接口非常简单,就是write和read,但他保存的对象是StateMachineContext,不是StateMachine,所以我们还不能直接用它,需要配置一下。

    import org.springframework.statemachine.StateMachinePersist;
    ......
    import org.springframework.statemachine.persist.StateMachinePersister;
    @Configuration
    public class PersistConfig {
        
        
        @Autowired
        private InMemoryStateMachinePersist inMemoryStateMachinePersist;
        
        /**
         * 注入StateMachinePersister对象
         * 
         * @return
         */
        @Bean(name="orderMemoryPersister")
        public StateMachinePersister<OrderStates, OrderEvents, String> getPersister() {
            return new DefaultStateMachinePersister<>(inMemoryStateMachinePersist);
        }
    
    }
    

    这里有个坑,InMemoryStateMachinePersist 实现的接口是

    org.springframework.statemachine.StateMachinePersist
    

    ,但在PersistConfig 里面,getPersister()方法返回的值类型是StateMachinePersister类型,看着很像,但并不是上面的这个接口,而是org.springframework.statemachine.persist.StateMachinePersister接口,为了表示这个坑对我的伤害,我要强调一下两个接口:

    package org.springframework.statemachine;
    public interface StateMachinePersist<S, E, T> {
        void write(StateMachineContext<S, E> context, T contextObj) throws Exception;
        StateMachineContext<S, E> read(T contextObj) throws Exception;
    }
    package org.springframework.statemachine.persist;
    import org.springframework.statemachine.StateMachine;
    public interface StateMachinePersister<S, E, T> {
        void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception;
        StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception;
    }
    

    这两个接口名字很类似,很容易搞混,但下面的是有er的,包名也不同的。StateMachinePersister是可以直接保存StateMachine对象的,所以我们需要先实现上面的StateMachinePersist,然后再一个Config类里面转换成下面的StateMachinePersister,转换的代码就在上面的PersistConfig类里。

    然后我们就能在controller里面使用了

    @Resource(name="orderMemoryPersister")
    private StateMachinePersister<OrderStates, OrderEvents, String> orderMemorypersister;
    
    ......
    
    //保存状态机
    @RequestMapping("/testMemoryPersister")
        public void tesMemorytPersister(String id) throws Exception {
            StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
            stateMachine.start();
            
            //发送PAY事件
            stateMachine.sendEvent(OrderEvents.PAY);
            Order order = new Order();
            order.setId(id);
            
            //持久化stateMachine
            orderMemorypersister.persist(stateMachine, order.getId());
        
        }
    
    //取出状态机
    @RequestMapping("/testMemoryPersisterRestore")
        public void testMemoryRestore(String id) throws Exception {
            StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
            orderMemorypersister.restore(stateMachine, id);
            System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
        }
    

    2、持久化到redis

      真正的业务中,一般都是多台机分布式运行,所以如果状态机只能保存在本地内容,就不能用在分布式应用上了。spring提供了一个方便的办法,使用redis解决这个问题。让我们看看怎么弄。
       pom文件引入spring-statemachine-redis

    <!-- redis持久化状态机 -->
            <dependency>
                <groupId>org.springframework.statemachine</groupId>
                <artifactId>spring-statemachine-redis</artifactId>
                <version>1.2.9.RELEASE</version>
            </dependency>
    

    在springboot配置文件里面加上redis参数,我这是application.properties

    # REDIS (RedisProperties)
    # Redis数据库索引(默认为0)
    spring.redis.database=0
    # Redis服务器地址
    spring.redis.host=localhost
    # Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    # 连接池最大连接数(使用负值表示没有限制)
    spring.redis.pool.max-active=8
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.pool.max-wait=-1
    # 连接池中的最大空闲连接
    spring.redis.pool.max-idle=8
    # 连接池中的最小空闲连接
    spring.redis.pool.min-idle=0
    # 连接超时时间(毫秒)
    spring.redis.timeout=0
    

    保证配置的redis开启并能用,我们继续。回到我们熟悉的PersistConfig

    @Configuration
    public class PersistConfig {
        
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
        
        
        /**
         * 注入RedisStateMachinePersister对象
         * 
         * @return
         */
        @Bean(name = "orderRedisPersister")
        public RedisStateMachinePersister<OrderStates, OrderEvents> redisPersister() {
            return new RedisStateMachinePersister<>(redisPersist());
        }
    
        /**
         * 通过redisConnectionFactory创建StateMachinePersist
         * 
         * @return
         */
        public StateMachinePersist<OrderStates, OrderEvents,String> redisPersist() {
            RedisStateMachineContextRepository<OrderStates, OrderEvents> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
            return new RepositoryStateMachinePersist<>(repository);
        }
        
    }
    

    这个套路和上面保存到本地内存是一样一样的,先生成一个StateMachinePersist,这里是通过RedisConnectionFactory生成RepositoryStateMachinePersist,然后再包装输出StateMachinePersister,这里是RedisStateMachinePersister。然后就可以愉快的在controller里面看怎么用了

    @Resource(name="orderRedisPersister")
    private StateMachinePersister<OrderStates, OrderEvents, String> orderRedisPersister;
    
    ......
    
    @RequestMapping("/testRedisPersister")
        public void testRedisPersister(String id) throws Exception {
            StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
            stateMachine.start();
            Order order = new Order();
            order.setId(id);
            //发送PAY事件
            Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.PAY).setHeader("order", order).build();
            stateMachine.sendEvent(message);
            //持久化stateMachine
            orderRedisPersister.persist(stateMachine, order.getId());
        }
        
        @RequestMapping("/testRedisPersisterRestore")
        public void testRestore(String id) throws Exception {
            StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
            orderRedisPersister.restore(stateMachine, id);
            System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
        }
    

    执行完redis保存statemachine后,大家可以自己在redis客户端查看以下,是不是有内容保存进去了。

    3.伪持久化和中间段的状态机

      我们设想一个业务场景,就比如订单吧,我们一般的设计都会把订单状态存到订单表里面,其他的业务信息也都有表保存,而状态机的主要作用其实是规范整个订单业务流程的状态和事件,所以状态机要不要保存真的不重要,我们只需要从订单表里面把状态取出来,知道当前是什么状态,然后伴随着业务继续流浪到下一个状态节点就好了。我们先实现一个StateMachinePersist,因为我不想真的持久化,所以就敷衍一下,持久化是什么,啥也不干。

    import org.springframework.statemachine.StateMachineContext;
    import org.springframework.statemachine.StateMachinePersist;
    import org.springframework.statemachine.support.DefaultStateMachineContext;
    import org.springframework.stereotype.Component;
    
    @Component
    public class OrderStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, Order> {
    
        @Override
        public void write(StateMachineContext<OrderStates, OrderEvents> context, Order contextObj) throws Exception {
            //这里不做任何持久化工作
        }
    
        @Override
        public StateMachineContext<OrderStates, OrderEvents> read(Order contextObj) throws Exception {
            StateMachineContext<OrderStates, OrderEvents> result = new DefaultStateMachineContext<OrderStates, OrderEvents>(OrderStates.valueOf(contextObj.getState()), 
                    null, null, null, null, "orderMachine");
            return result;
        }
    }
    

    然后在PersistConfig里面转换成StateMachinePersister

    @Configuration
    public class PersistConfig {
    @Autowired
        private OrderStateMachinePersist orderStateMachinePersist;
    @Bean(name="orderPersister")
        public StateMachinePersister<OrderStates, OrderEvents, Order> orderPersister() {
            return new DefaultStateMachinePersister<OrderStates, OrderEvents, Order>(orderStateMachinePersist);
        }
    }
    

    现在问题来了,不持久化的持久化类是为啥呢,主要就是为了取一个任何状态节点的状态机,方便继续往下执行,请看controller

    @RestController
    @RequestMapping("/statemachine")
    public class StateMachineController {
    
        @Resource(name="orderPersister")
        private StateMachinePersister<OrderStates, OrderEvents, Order> persister;
        
        @RequestMapping("/testOrderRestore")
        public void testOrderRestore(String id) throws Exception {
            StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
            //订单
            Order order = new Order();
            order.setId(id);
            order.setState(OrderStates.WAITING_FOR_RECEIVE.toString());
            //恢复
            persister.restore(stateMachine, order);
            //查看恢复后状态机的状态
            System.out.println("恢复后的状态:" + stateMachine.getState().getId());
        }
    }
    

      看到没有,用builder建了一个新的状态机,用restore过了一手,就已经是一个到达order指定状态的老司机状态机了,在这里,持久化不是本意,让状态机能够随时抓换到任意状态节点才是目的。在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程......,所以这种可以任意调节状态的才是我们需要的状态机。

    相关文章

      网友评论

          本文标题:spring statemachine-传递参数的message

          本文链接:https://www.haomeiwen.com/subject/ljizwhtx.html