摘要:目前為止,我們都是從狀態流程的開始階段創建一個狀態機,然后一路走下去。然后就可以愉快的在里面看怎么用了發送事件持久化恢復狀態機后的狀態為執行完保存后,大家可以自己在客戶端查看以下,是不是有內容保存進去了。
目前為止,我們都是從狀態流程的開始階段創建一個狀態機,然后一路走下去。但在實際業務中,狀態機可能需要在某個環節停留,等待其他業務的觸發,然后再繼續下面的流程。比如訂單,可能在支付環節需要等待一個剁手的用戶隔天再下單,所以這里面涉及到一個創建的狀態機該何去何從的問題。在spring statemachine中,給出來的辦法就是保存起來,到需要的時候取出來用。
1、持久化到本地內存
嚴格來說,你完全可以自己保存狀態機,比如我就自己用map保存下來了。
public class MachineMap {
public static Map> orderMap = new HashMap >(); public static Map > formMap = new HashMap >();
}
這個代碼一看就明白,我用唯一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
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 實現的接口是
org.springframework.statemachine.StateMachinePersist
,但在PersistConfig 里面,getPersister()方法返回的值類型是StateMachinePersister類型,看著很像,但并不是上面的這個接口,而是org.springframework.statemachine.persist.StateMachinePersister接口,為了表示這個坑對我的傷害,我要強調一下兩個接口:
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對象的,所以我們需要先實現上面的StateMachinePersist,然后再一個Config類里面轉換成下面的StateMachinePersister,轉換的代碼就在上面的PersistConfig類里。
然后我們就能在controller里面使用了
@Resource(name="orderMemoryPersister")
private StateMachinePersister
......
//保存狀態機
@RequestMapping("/testMemoryPersister")
public void tesMemorytPersister(String id) throws Exception { StateMachinestateMachine = 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 { StateMachinestateMachine = orderStateMachineBuilder.build(beanFactory); orderMemorypersister.restore(stateMachine, id); System.out.println("恢復狀態機后的狀態為:" + stateMachine.getState().getId()); }
2、持久化到redis
真正的業務中,一般都是多臺機分布式運行,所以如果狀態機只能保存在本地內容,就不能用在分布式應用上了。spring提供了一個方便的辦法,使用redis解決這個問題。讓我們看看怎么弄。
pom文件引入spring-statemachine-redis
org.springframework.statemachine spring-statemachine-redis 1.2.9.RELEASE
在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 RedisStateMachinePersisterredisPersister() { return new RedisStateMachinePersister<>(redisPersist()); } /** * 通過redisConnectionFactory創建StateMachinePersist * * @return */ public StateMachinePersist redisPersist() { RedisStateMachineContextRepository repository = new RedisStateMachineContextRepository<>(redisConnectionFactory); return new RepositoryStateMachinePersist<>(repository); }
}
這個套路和上面保存到本地內存是一樣一樣的,先生成一個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); //發送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("恢復狀態機后的狀態為:" + stateMachine.getState().getId()); }
執行完redis保存statemachine后,大家可以自己在redis客戶端查看以下,是不是有內容保存進去了。
3、下期預告
無論是本地內存還是分布式緩存,似乎都不是很可靠的持久化,但下一章會告訴大家,保不保存也就那么點大的事,沒保存我們就再創建一個就好了。但另外一個問題就浮出水面了,狀態機執行到中間的時候創建狀態機,怎么在中間繼續執行,之前的可都是從頭開始執行的,這個問題,下一章解決。
碼云配套代碼地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74992.html
摘要:在實際的企業開發中,不可能所有情況都是從頭到尾的按狀態流程來,會有很多意外,比如歷史數據,故障重啟后的遺留流程,所以這種可以任意調節狀態的才是我們需要的狀態機。 1、偽持久化和中間段的狀態機我們設想一個業務場景,就比如訂單吧,我們一般的設計都會把訂單狀態存到訂單表里面,其他的業務信息也都有表保存,而狀態機的主要作用其實是規范整個訂單業務流程的狀態和事件,所以狀態機要不要保存真的不重要,...
摘要:創建了后,狀態機就可以不只是傳一個,可以組合和數據內容一起發送給狀態機變化的處理類了。到這里為止,狀態機通過對象就和其他的業務代碼做到了數據連接。 在企業開發中,數據在不同的業務間傳輸是最常見的工作,所以雖然我們的主架構是用的狀態機,也就是從流程狀態的角度來看待這個項目,但在具體業務中,每個狀態的轉變中會牽涉到各類業務,這些業務有些需要收到狀態機變化的通知,需要把狀態值傳遞給業務類和業...
摘要:雖然多個狀態機的問題解決了,但是對于實際的企業應用而言,還是有問題。這個問題就用到了狀態機的持久化,我們下一章就談談持久化問題。 1、多個狀態機的搞法在實際的企業應用中,基本不可能只有一個狀態機流程在跑,比如訂單,肯定是很多個訂單在運行,每個訂單都有自己的訂單狀態機流程,但上一章的例子,大家可以試一下,當執行到一個狀態時,再次刷新頁面,不會有任何日志出現,當一個狀態流程執行到某個狀態,...
摘要:讓我們先看下狀態機的概念。下面是狀態機模型中的個要素,即現態條件動作次態。因為訂單和審批公文都有很多的流程,每個流程都會產生狀態的變化,而且流程是這種業務的主軸,其他都是圍繞這個流程和狀態變化來考慮的,所以看起來蠻適合用狀態機來做。 1、背景在我打算學習spring statemachine的時候,我幾乎看過了所有網上的中文教程,基本上都處于淺嘗輒止的階段,有幾篇講的比較深入的,都只是...
摘要:目前為止,多個狀態機和多種狀態機都可以在里面實現了,下一章我們來解決下狀態機和實際業務間的數據傳輸問題,畢竟我們不是為了讓狀態機自個獨自玩耍,和業務數據互通有無才是企業開發的正道。 在上一章的例子中,我們實現了多個狀態機并存執行,不同的訂單有各自的狀態機運行,但只有一種狀態機,這顯然不能滿足實際業務的要求,比如我就遇到了訂單流程和公文審批流程在同一個項目的情況,所以我們這一章講怎么讓多...
閱讀 1459·2021-10-18 13:29
閱讀 2684·2021-10-12 10:18
閱讀 3580·2021-09-22 15:06
閱讀 2596·2019-08-29 17:09
閱讀 2787·2019-08-29 16:41
閱讀 1493·2019-08-29 13:48
閱讀 3226·2019-08-26 13:49
閱讀 3325·2019-08-26 13:34