摘要:目前為止,我們都是從狀態(tài)流程的開始階段創(chuàng)建一個狀態(tài)機(jī),然后一路走下去。然后就可以愉快的在里面看怎么用了發(fā)送事件持久化恢復(fù)狀態(tài)機(jī)后的狀態(tài)為執(zhí)行完保存后,大家可以自己在客戶端查看以下,是不是有內(nèi)容保存進(jìn)去了。
目前為止,我們都是從狀態(tài)流程的開始階段創(chuàng)建一個狀態(tài)機(jī),然后一路走下去。但在實(shí)際業(yè)務(wù)中,狀態(tài)機(jī)可能需要在某個環(huán)節(jié)停留,等待其他業(yè)務(wù)的觸發(fā),然后再繼續(xù)下面的流程。比如訂單,可能在支付環(huán)節(jié)需要等待一個剁手的用戶隔天再下單,所以這里面涉及到一個創(chuàng)建的狀態(tài)機(jī)該何去何從的問題。在spring statemachine中,給出來的辦法就是保存起來,到需要的時候取出來用。
1、持久化到本地內(nèi)存
嚴(yán)格來說,你完全可以自己保存狀態(tài)機(jī),比如我就自己用map保存下來了。
public class MachineMap {
public static Map> orderMap = new HashMap >(); public static Map > formMap = new HashMap >();
}
這個代碼一看就明白,我用唯一id作為key(order就是orderId,form就是formId),把狀態(tài)機(jī)保存到map表里面,在實(shí)際的業(yè)務(wù)中,自由存取
@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+"轉(zhuǎn)換"); 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+"轉(zhuǎn)換"); return; } } Message message = MessageBuilder.withPayload(OrderEvents.valueOf(events)).setHeader("order", order).setHeader("test","test1").build(); sm.sendEvent(message); } }
在這段代碼里面,我根據(jù)不同種類的狀態(tài)機(jī)去map取和id對應(yīng)的狀態(tài)機(jī),如果狀態(tài)機(jī)沒在map里面,就創(chuàng)建一個,然后塞進(jìn)map表。其實(shí)寫這個代碼,并沒有其他意思,只是為了告訴你,spring打算管理你的map表,所以出了一個接口來規(guī)范這件事:
import java.util.HashMap;
import java.util.Map;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.stereotype.Component;
/**
在內(nèi)存中持久化狀態(tài)機(jī)
*/
@Component
public class InMemoryStateMachinePersist implements StateMachinePersist
private Map> map = new HashMap >(); @Override public void write(StateMachineContext context, String contextObj) throws Exception { map.put(contextObj, context); } @Override public StateMachineContext 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 StateMachinePersistergetPersister() { return new DefaultStateMachinePersister<>(inMemoryStateMachinePersist); }
}
這里有個坑,InMemoryStateMachinePersist 實(shí)現(xiàn)的接口是
org.springframework.statemachine.StateMachinePersist
,但在PersistConfig 里面,getPersister()方法返回的值類型是StateMachinePersister類型,看著很像,但并不是上面的這個接口,而是org.springframework.statemachine.persist.StateMachinePersister接口,為了表示這個坑對我的傷害,我要強(qiáng)調(diào)一下兩個接口:
package org.springframework.statemachine;
public interface StateMachinePersist {
void write(StateMachineContextcontext, T contextObj) throws Exception; StateMachineContextread(T contextObj) throws Exception;
}
package org.springframework.statemachine.persist;
import org.springframework.statemachine.StateMachine;
public interface StateMachinePersister {
void persist(StateMachinestateMachine, T contextObj) throws Exception; StateMachinerestore(StateMachinestateMachine, T contextObj) throws Exception;
}
這兩個接口名字很類似,很容易搞混,但下面的是有er的,包名也不同的。StateMachinePersister是可以直接保存StateMachine對象的,所以我們需要先實(shí)現(xiàn)上面的StateMachinePersist,然后再一個Config類里面轉(zhuǎn)換成下面的StateMachinePersister,轉(zhuǎn)換的代碼就在上面的PersistConfig類里。
然后我們就能在controller里面使用了
@Resource(name="orderMemoryPersister")
private StateMachinePersister
......
//保存狀態(tài)機(jī)
@RequestMapping("/testMemoryPersister")
public void tesMemorytPersister(String id) throws Exception { StateMachinestateMachine = orderStateMachineBuilder.build(beanFactory); stateMachine.start(); //發(fā)送PAY事件 stateMachine.sendEvent(OrderEvents.PAY); Order order = new Order(); order.setId(id); //持久化stateMachine orderMemorypersister.persist(stateMachine, order.getId()); }
//取出狀態(tài)機(jī)
@RequestMapping("/testMemoryPersisterRestore")
public void testMemoryRestore(String id) throws Exception { StateMachinestateMachine = orderStateMachineBuilder.build(beanFactory); orderMemorypersister.restore(stateMachine, id); System.out.println("恢復(fù)狀態(tài)機(jī)后的狀態(tài)為:" + stateMachine.getState().getId()); }
2、持久化到redis
真正的業(yè)務(wù)中,一般都是多臺機(jī)分布式運(yùn)行,所以如果狀態(tài)機(jī)只能保存在本地內(nèi)容,就不能用在分布式應(yīng)用上了。spring提供了一個方便的辦法,使用redis解決這個問題。讓我們看看怎么弄。
pom文件引入spring-statemachine-redis
org.springframework.statemachine spring-statemachine-redis 1.2.9.RELEASE
在springboot配置文件里面加上redis參數(shù),我這是application.properties
REDIS (RedisProperties) Redis數(shù)據(jù)庫索引(默認(rèn)為0)spring.redis.database=0
Redis服務(wù)器地址spring.redis.host=localhost
Redis服務(wù)器連接端口spring.redis.port=6379
Redis服務(wù)器連接密碼(默認(rèn)為空)spring.redis.password=
連接池最大連接數(shù)(使用負(fù)值表示沒有限制)spring.redis.pool.max-active=8
連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)spring.redis.pool.max-wait=-1
連接池中的最大空閑連接spring.redis.pool.max-idle=8
連接池中的最小空閑連接spring.redis.pool.min-idle=0
連接超時時間(毫秒)spring.redis.timeout=0
保證配置的redis開啟并能用,我們繼續(xù)?;氐轿覀兪煜さ腜ersistConfig
@Configuration
public class PersistConfig {
@Autowired private RedisConnectionFactory redisConnectionFactory; /** * 注入RedisStateMachinePersister對象 * * @return */ @Bean(name = "orderRedisPersister") public RedisStateMachinePersisterredisPersister() { return new RedisStateMachinePersister<>(redisPersist()); } /** * 通過redisConnectionFactory創(chuàng)建StateMachinePersist * * @return */ public StateMachinePersist redisPersist() { RedisStateMachineContextRepository repository = new RedisStateMachineContextRepository<>(redisConnectionFactory); return new RepositoryStateMachinePersist<>(repository); }
}
這個套路和上面保存到本地內(nèi)存是一樣一樣的,先生成一個StateMachinePersist,這里是通過RedisConnectionFactory生成RepositoryStateMachinePersist,然后再包裝輸出StateMachinePersister,這里是RedisStateMachinePersister。然后就可以愉快的在controller里面看怎么用了
@Resource(name="orderRedisPersister")
private StateMachinePersister
......
@RequestMapping("/testRedisPersister")
public void testRedisPersister(String id) throws Exception { StateMachinestateMachine = orderStateMachineBuilder.build(beanFactory); stateMachine.start(); Order order = new Order(); order.setId(id); //發(fā)送PAY事件 Message 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 stateMachine = orderStateMachineBuilder.build(beanFactory); orderRedisPersister.restore(stateMachine, id); System.out.println("恢復(fù)狀態(tài)機(jī)后的狀態(tài)為:" + stateMachine.getState().getId()); }
執(zhí)行完redis保存statemachine后,大家可以自己在redis客戶端查看以下,是不是有內(nèi)容保存進(jìn)去了。
3、下期預(yù)告
無論是本地內(nèi)存還是分布式緩存,似乎都不是很可靠的持久化,但下一章會告訴大家,保不保存也就那么點(diǎn)大的事,沒保存我們就再創(chuàng)建一個就好了。但另外一個問題就浮出水面了,狀態(tài)機(jī)執(zhí)行到中間的時候創(chuàng)建狀態(tài)機(jī),怎么在中間繼續(xù)執(zhí)行,之前的可都是從頭開始執(zhí)行的,這個問題,下一章解決。
碼云配套代碼地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74992.html
摘要:在實(shí)際的企業(yè)開發(fā)中,不可能所有情況都是從頭到尾的按狀態(tài)流程來,會有很多意外,比如歷史數(shù)據(jù),故障重啟后的遺留流程,所以這種可以任意調(diào)節(jié)狀態(tài)的才是我們需要的狀態(tài)機(jī)。 1、偽持久化和中間段的狀態(tài)機(jī)我們設(shè)想一個業(yè)務(wù)場景,就比如訂單吧,我們一般的設(shè)計(jì)都會把訂單狀態(tài)存到訂單表里面,其他的業(yè)務(wù)信息也都有表保存,而狀態(tài)機(jī)的主要作用其實(shí)是規(guī)范整個訂單業(yè)務(wù)流程的狀態(tài)和事件,所以狀態(tài)機(jī)要不要保存真的不重要,...
摘要:創(chuàng)建了后,狀態(tài)機(jī)就可以不只是傳一個,可以組合和數(shù)據(jù)內(nèi)容一起發(fā)送給狀態(tài)機(jī)變化的處理類了。到這里為止,狀態(tài)機(jī)通過對象就和其他的業(yè)務(wù)代碼做到了數(shù)據(jù)連接。 在企業(yè)開發(fā)中,數(shù)據(jù)在不同的業(yè)務(wù)間傳輸是最常見的工作,所以雖然我們的主架構(gòu)是用的狀態(tài)機(jī),也就是從流程狀態(tài)的角度來看待這個項(xiàng)目,但在具體業(yè)務(wù)中,每個狀態(tài)的轉(zhuǎn)變中會牽涉到各類業(yè)務(wù),這些業(yè)務(wù)有些需要收到狀態(tài)機(jī)變化的通知,需要把狀態(tài)值傳遞給業(yè)務(wù)類和業(yè)...
摘要:雖然多個狀態(tài)機(jī)的問題解決了,但是對于實(shí)際的企業(yè)應(yīng)用而言,還是有問題。這個問題就用到了狀態(tài)機(jī)的持久化,我們下一章就談?wù)劤志没瘑栴}。 1、多個狀態(tài)機(jī)的搞法在實(shí)際的企業(yè)應(yīng)用中,基本不可能只有一個狀態(tài)機(jī)流程在跑,比如訂單,肯定是很多個訂單在運(yùn)行,每個訂單都有自己的訂單狀態(tài)機(jī)流程,但上一章的例子,大家可以試一下,當(dāng)執(zhí)行到一個狀態(tài)時,再次刷新頁面,不會有任何日志出現(xiàn),當(dāng)一個狀態(tài)流程執(zhí)行到某個狀態(tài),...
摘要:讓我們先看下狀態(tài)機(jī)的概念。下面是狀態(tài)機(jī)模型中的個要素,即現(xiàn)態(tài)條件動作次態(tài)。因?yàn)橛唵魏蛯徟亩加泻芏嗟牧鞒蹋總€流程都會產(chǎn)生狀態(tài)的變化,而且流程是這種業(yè)務(wù)的主軸,其他都是圍繞這個流程和狀態(tài)變化來考慮的,所以看起來蠻適合用狀態(tài)機(jī)來做。 1、背景在我打算學(xué)習(xí)spring statemachine的時候,我?guī)缀蹩催^了所有網(wǎng)上的中文教程,基本上都處于淺嘗輒止的階段,有幾篇講的比較深入的,都只是...
摘要:目前為止,多個狀態(tài)機(jī)和多種狀態(tài)機(jī)都可以在里面實(shí)現(xiàn)了,下一章我們來解決下狀態(tài)機(jī)和實(shí)際業(yè)務(wù)間的數(shù)據(jù)傳輸問題,畢竟我們不是為了讓狀態(tài)機(jī)自個獨(dú)自玩耍,和業(yè)務(wù)數(shù)據(jù)互通有無才是企業(yè)開發(fā)的正道。 在上一章的例子中,我們實(shí)現(xiàn)了多個狀態(tài)機(jī)并存執(zhí)行,不同的訂單有各自的狀態(tài)機(jī)運(yùn)行,但只有一種狀態(tài)機(jī),這顯然不能滿足實(shí)際業(yè)務(wù)的要求,比如我就遇到了訂單流程和公文審批流程在同一個項(xiàng)目的情況,所以我們這一章講怎么讓多...
閱讀 1496·2021-10-18 13:29
閱讀 2826·2021-10-12 10:18
閱讀 3616·2021-09-22 15:06
閱讀 2621·2019-08-29 17:09
閱讀 2829·2019-08-29 16:41
閱讀 1525·2019-08-29 13:48
閱讀 3253·2019-08-26 13:49
閱讀 3352·2019-08-26 13:34