在實際業務中,狀态機可能需要在某個環節停留,等待其他業務的觸發,然後再繼續下面的流程。比如訂單,可能在支付環節需要等待一個剁手的使用者隔天再下單,是以這裡面涉及到一個建立的狀态機該何去何從的問題。在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指定狀态的老司機狀态機了,在這裡,持久化不是本意,讓狀态機能夠随時抓換到任意狀态節點才是目的。在實際的企業開發中,不可能所有情況都是從頭到尾的按狀态流程來,會有很多意外,比如曆史資料,故障重新開機後的遺留流程…,是以這種可以任意調節狀态的才是我們需要的狀态機。