在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付环节需要等待一个剁手的用户隔天再下单,所以这里面涉及到一个创建的状态机该何去何从的问题。在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中可以看到保存的信息。
伪持久化
我们设想一个业务场景,就比如订单吧,我们一般的设计都会把订单状态存到订单表里面,其他的业务信息也都有表保存,而状态机的主要作用其实是规范整个订单业务流程的状态和事件,所以状态机要不要保存真的不重要,我们只需要从订单表里面把状态取出来,知道当前是什么状态,然后伴随着业务继续流浪到下一个状态节点就好了。
我们先实现一个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指定状态的老司机状态机了,在这里,持久化不是本意,让状态机能够随时抓换到任意状态节点才是目的。在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程…,所以这种可以任意调节状态的才是我们需要的状态机。