spring statemachine-传递参数的message与持久化

1.传递参数的message

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

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

@RequestMapping("/testOrderState")
    public void testOrderState(String orderId) throws Exception {

        StateMachine 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 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 {

    /**
     * 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 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 message) {
        System.out.println("传递的参数:" + message.getHeaders().get("order"));
        logger.info("---用户已收货,订单完成---");
    }

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

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

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

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

Message 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 {

    private Map> map = new HashMap>();
    
    @Override
    public void write(StateMachineContext context, String contextObj) throws Exception {
        map.put(contextObj, context);
    }

    @Override
    public StateMachineContext 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 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 {
    void write(StateMachineContext context, T contextObj) throws Exception;
    StateMachineContext read(T contextObj) throws Exception;
}
package org.springframework.statemachine.persist;
import org.springframework.statemachine.StateMachine;
public interface StateMachinePersister {
    void persist(StateMachine stateMachine, T contextObj) throws Exception;
    StateMachine restore(StateMachine stateMachine, T contextObj) throws Exception;
}

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

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

@Resource(name="orderMemoryPersister")
private StateMachinePersister orderMemorypersister;

......

//保存状态机
@RequestMapping("/testMemoryPersister")
    public void tesMemorytPersister(String id) throws Exception {
        StateMachine 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 stateMachine = orderStateMachineBuilder.build(beanFactory);
        orderMemorypersister.restore(stateMachine, id);
        System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
    }

2、持久化到redis

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


        
            org.springframework.statemachine
            spring-statemachine-redis
            1.2.9.RELEASE
        

在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 redisPersister() {
        return new RedisStateMachinePersister<>(redisPersist());
    }

    /**
     * 通过redisConnectionFactory创建StateMachinePersist
     * 
     * @return
     */
    public StateMachinePersist redisPersist() {
        RedisStateMachineContextRepository repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
        return new RepositoryStateMachinePersist<>(repository);
    }
    
}

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

@Resource(name="orderRedisPersister")
private StateMachinePersister orderRedisPersister;

......

@RequestMapping("/testRedisPersister")
    public void testRedisPersister(String id) throws Exception {
        StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
        stateMachine.start();
        Order order = new Order();
        order.setId(id);
        //发送PAY事件
        Message 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 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 {

    @Override
    public void write(StateMachineContext context, Order contextObj) throws Exception {
        //这里不做任何持久化工作
    }

    @Override
    public StateMachineContext read(Order contextObj) throws Exception {
        StateMachineContext result = new DefaultStateMachineContext(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 orderPersister() {
        return new DefaultStateMachinePersister(orderStateMachinePersist);
    }
}

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

@RestController
@RequestMapping("/statemachine")
public class StateMachineController {

    @Resource(name="orderPersister")
    private StateMachinePersister persister;
    
    @RequestMapping("/testOrderRestore")
    public void testOrderRestore(String id) throws Exception {
        StateMachine 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与持久化)