目前为止,我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。但在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付环节需要等待一个剁手的用户隔天再下单,所以这里面涉及到一个创建的状态机该何去何从的问题。在 spring statemachine 中,给出来的办法就是保存起来,到需要的时候取出来用。
1、持久化到本地内存
严格来说,你完全可以自己保存状态机,比如我就自己用 map 保存下来了。
public class MachineMap {
public static Map<String,StateMachine<OrderStates, OrderEvents>> orderMap = new HashMap<String,StateMachine<OrderStates, OrderEvents>>();
public static Map<String,StateMachine<FormStates, FormEvents>> formMap = new HashMap<String,StateMachine<FormStates, FormEvents>>();
}
这个代码一看就明白,我用唯一 id 作为 key(order 就是 orderId,form 就是 formId),把状态机保存到 map 表里面,在实际的业务中,自由存取
@RequestMapping(“/sendEvent”)
void sendEvent(String machineId,String events,String id) throws Exception{if(machineId.equals("form")) {StateMachine sm = MachineMap.formMap.get(id);
Form form = new Form();
form.setId(id);
if(sm == null) {if(events.equals("WRITE")) {sm = formStateMachineBuilder.build(beanFactory);
sm.start();
MachineMap.formMap.put(id, sm);
}else {System.out.println("该表单流程尚未开始,不能做"+events+"转换");
return;
}
}
Message message = MessageBuilder.withPayload(FormEvents.valueOf(events)).setHeader("form", form).build();
sm.sendEvent(message);
}
if(machineId.equals("order")) {StateMachine sm = MachineMap.orderMap.get(id);
Order order = new Order();
order.setId(id);
if(sm == null) {if(events.equals("PAY")) {sm = orderStateMachineBuilder.build(beanFactory);
sm.start();
MachineMap.orderMap.put(id, sm);
}else {System.out.println("该订单流程尚未开始,不能做"+events+"转换");
return;
}
}
Message message = MessageBuilder.withPayload(OrderEvents.valueOf(events)).setHeader("order", order).setHeader("test","test1").build();
sm.sendEvent(message);
}
}
在这段代码里面,我根据不同种类的状态机去 map 取和 id 对应的状态机,如果状态机没在 map 里面,就创建一个,然后塞进 map 表。其实写这个代码,并没有其他意思,只是为了告诉你,spring 打算管理你的 map 表,所以出了一个接口来规范这件事:
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、下期预告
无论是本地内存还是分布式缓存,似乎都不是很可靠的持久化,但下一章会告诉大家,保不保存也就那么点大的事,没保存我们就再创建一个就好了。但另外一个问题就浮出水面了,状态机执行到中间的时候创建状态机,怎么在中间继续执行,之前的可都是从头开始执行的,这个问题,下一章解决。
码云配套代码地址