目前为止,咱们都是从状态流程的开始阶段建立一个状态机,而后一路走下去。但在实际业务中,状态机可能须要在某个环节停留,等待其余业务的触发,而后再继续下面的流程。好比订单,可能在支付环节须要等待一个剁手的用户隔天再下单,因此这里面涉及到一个建立的状态机该何去何从的问题。在spring statemachine中,给出来的办法就是保存起来,到须要的时候取出来用。java
严格来讲,你彻底能够本身保存状态机,好比我就本身用map保存下来了。git
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表里面,在实际的业务中,自由存取redis
@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表,因此出了一个接口来规范这件事:spring
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接口,为了表示这个坑对个人伤害,我要强调一下两个接口:springboot
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里面使用了app
@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()); }
真正的业务中,通常都是多台机分布式运行,因此若是状态机只能保存在本地内容,就不能用在分布式应用上了。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客户端查看如下,是否是有内容保存进去了。
不管是本地内存仍是分布式缓存,彷佛都不是很可靠的持久化,但下一章会告诉你们,保不保存也就那么点大的事,没保存咱们就再建立一个就行了。但另一个问题就浮出水面了,状态机执行到中间的时候建立状态机,怎么在中间继续执行,以前的可都是从头开始执行的,这个问题,下一章解决。