天天看点

Spring StateMachine学习(四) —— 持久化

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

持久化到本地内存

首先要实现

StateMachinePersist

接口,这个接口非常简单,就是write和read,用来读写

StateMachineContext

@Component
public class InMemoryStateMachinePersist implements StateMachinePersist<RefundReasonStatus, RefundReasonEvents, String> {
    private Map<String, StateMachineContext<RefundReasonStatus, RefundReasonEvents>> refundReasonMap =
            new HashMap<>();

    @Override
    public void write(StateMachineContext<RefundReasonStatus, RefundReasonEvents> stateMachineContext, String orderSn) throws Exception {
        refundReasonMap.put(orderSn, stateMachineContext);
    }

    @Override
    public StateMachineContext<RefundReasonStatus, RefundReasonEvents> read(String orderSn) throws Exception {
        return refundReasonMap.get(orderSn);
    }
}
           

InMemoryStateMachinePersist

保存的对象是StateMachineContext,不是StateMachine,所以我们还不能直接用它,需要配置一下。

@Configuration
public class PersistConfig {


    @Autowired
    private InMemoryStateMachinePersist inMemoryStateMachinePersist;

    /**
     * 注入StateMachinePersister对象
     *
     * @return
     */
    @Bean(name="orderMemoryPersister")
    public StateMachinePersister<RefundReasonStatus, RefundReasonEvents, String> getPersister() {
        return new DefaultStateMachinePersister<>(inMemoryStateMachinePersist);
    }

}
           

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

@Autowired
    @Qualifier("orderMemoryPersister")
    private StateMachinePersister<RefundReasonStatus, RefundReasonEvents, String> orderMemorypersister;
    
    //保存状态机
    @GetMapping("/testMemoryPersister")
    public void tesMemorytPersister(@RequestParam String orderSn) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        stateMachine.start();

        stateMachine.sendEvent(RefundReasonEvents.REJECT);
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setMobile("13613650996");
        order.setSkuId(98765L);

        //持久化stateMachine
        orderMemorypersister.persist(stateMachine, orderSn);
    }

    //取出状态机
    @GetMapping("/testMemoryPersisterRestore")
    public void testMemoryRestore(String id) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        orderMemorypersister.restore(stateMachine, id);
        log.info("恢复状态机后的状态为:{}", stateMachine.getState().getId());
    }
           

持久化到redis

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

pom文件引入

spring-statemachine-redis

<dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-redis</artifactId>
            <version>1.2.14.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

@Autowired
    private RedisConnectionFactory redisConnectionFactory;
    
    /**
     * 注入RedisStateMachinePersister对象
     *
     * @return
     */
    @Bean(name = "orderRedisPersister")
    public RedisStateMachinePersister<RefundReasonStatus, RefundReasonEvents> redisPersister() {
        return new RedisStateMachinePersister<>(redisPersist());
    }

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

这个套路和上面保存到本地内存是一样一样的,先生成一个StateMachinePersist,这里是通过RedisConnectionFactory生成RepositoryStateMachinePersist,然后再包装输出StateMachinePersister,这里是RedisStateMachinePersister。

使用redis保存和读取状态机

@Autowired
    @Qualifier("orderRedisPersister")
    private StateMachinePersister<RefundReasonStatus, RefundReasonEvents, String> orderRedisPersister;
    
    @GetMapping("/testRedisPersister")
    public void testRedisPersister(@RequestParam String orderSn) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        stateMachine.start();

        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setMobile("13613650996");
        order.setSkuId(98765L);
        Message<RefundReasonEvents> message = MessageBuilder.withPayload(RefundReasonEvents.APPROVE)
                .setHeader("order", order).build();
        stateMachine.sendEvent(message);
        //持久化stateMachine
        orderRedisPersister.persist(stateMachine, order.getOrderSn());
    }

    @GetMapping("/testRedisPersisterRestore")
    public void testRestore(String id) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        orderRedisPersister.restore(stateMachine, id);
        System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
    }
           

从redis中可以看到保存的信息。

Spring StateMachine学习(四) —— 持久化

伪持久化

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

我们先实现一个StateMachinePersist。这里并不需要持久化。

@Component
public class OrderStateMachinePersist implements StateMachinePersist<RefundReasonStatus, RefundReasonEvents, Order> {

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

    @Override
    public StateMachineContext<RefundReasonStatus, RefundReasonEvents> read(Order order) throws Exception {
        StateMachineContext<RefundReasonStatus, RefundReasonEvents> result =
                new DefaultStateMachineContext<RefundReasonStatus, RefundReasonEvents>(RefundReasonStatus.valueOf(order.getStatus()),
                null, null, null, null, "refundReasonMachine");
        return result;
    }
}
           

然后在PersistConfig里面转换成StateMachinePersister。

@Autowired
    private OrderStateMachinePersist orderStateMachinePersist;

    @Bean(name="orderPersister")
    public StateMachinePersister<RefundReasonStatus, RefundReasonEvents, Order> orderPersister() {
        return new DefaultStateMachinePersister(orderStateMachinePersist);
    }
           

这样主要是为了取一个任何状态节点的状态机。

@Autowired
    @Qualifier("orderPersister")
    private StateMachinePersister<RefundReasonStatus, RefundReasonEvents, Order> persister;
    
    @GetMapping("/testOrderRestore")
    public void testOrderRestore(@RequestParam String orderSn) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        //订单
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setStatus(4);
        //恢复
        persister.restore(stateMachine, order);
        //查看恢复后状态机的状态
        System.out.println("恢复后的状态:" + stateMachine.getState().getId());
    }
           

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