1.傳遞參數(shù)的message
?? 在企業(yè)開發(fā)中,數(shù)據(jù)在不同的業(yè)務(wù)間傳輸是最常見的工作叠赐,所以雖然我們的主架構(gòu)是用的狀態(tài)機欲账,也就是從流程狀態(tài)的角度來看待這個項目,但在具體業(yè)務(wù)中芭概,每個狀態(tài)的轉(zhuǎn)變中會牽涉到各類業(yè)務(wù)赛不,這些業(yè)務(wù)有些需要收到狀態(tài)機變化的通知,需要把狀態(tài)值傳遞給業(yè)務(wù)類和業(yè)務(wù)方法谈山,同樣的俄删,在處理狀態(tài)變化是,也需要獲取業(yè)務(wù)數(shù)據(jù)奏路,方便不同的業(yè)務(wù)在同一個狀態(tài)變化環(huán)節(jié)做各自的業(yè)務(wù)畴椰,下面我們就講下這個數(shù)據(jù)在spring statemachine里面的傳遞。
?? 這次我們的順序變一下鸽粉,由外部傳入一個訂單號到controller開始:
@RequestMapping("/testOrderState")
public void testOrderState(String orderId) throws Exception {
StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
System.out.println(stateMachine.getId());
// 創(chuàng)建流程
stateMachine.start();
// 觸發(fā)PAY事件
stateMachine.sendEvent(OrderEvents.PAY);
// 觸發(fā)RECEIVE事件
Order order = new Order(orderId, "547568678", "廣東省深圳市", "13435465465", "RECEIVE");
Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).build();
stateMachine.sendEvent(message);
// 獲取最終狀態(tài)
System.out.println("最終狀態(tài):" + stateMachine.getState().getId());
}
?? controller收到request請求的參數(shù)斜脂,orderId,然后狀態(tài)機依次觸發(fā)事件触机,到觸發(fā)RECEIVE事件的時候帚戳,我們新建了一個Order玷或,并把orderId塞進去了,其實更多的情況應(yīng)該是我們拿到orderId片任,然后查詢數(shù)據(jù)庫偏友,得到order數(shù)據(jù)對象,這里為了簡化代碼对供,就新建一個啦位他。
?? 然后就是真正的主角登場了,Message产场。它其實不是spirng statemachine專屬的鹅髓,它是spring里面通用的一種消息工具,看它的源代碼:
package org.springframework.messaging;
public interface Message<T> {
/**
* Return the message payload.
*/
T getPayload();
/**
* Return message headers for the message (never {@code null} but may be empty).
*/
MessageHeaders getHeaders();
}
它由兩個部分組成京景,看圖就知道了窿冯,和代碼里面是一致的
?? 在spring statemachine里面,我們把狀態(tài)塞到message的payload里面确徙,然后把需要傳遞的業(yè)務(wù)數(shù)據(jù)(例子里面就是order對象)塞到header里面醒串。創(chuàng)建message用的是messagebuilder,看它的名字就知道是專門創(chuàng)建message的鄙皇。
Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).build();
stateMachine.sendEvent(message);
?? 創(chuàng)建了message后厦凤,狀態(tài)機sendEvent就可以不只是傳一個event,可以組合event(OrderEvents.RECEIVE)和數(shù)據(jù)內(nèi)容(order)一起發(fā)送給狀態(tài)機變化的處理類eventconfig了育苟。讓我們看eventConfig的處理:
/**
* WAITING_FOR_RECEIVE->DONE 執(zhí)行的動作
*/
@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive(Message<OrderEvents> message) {
System.out.println("傳遞的參數(shù):" + message.getHeaders().get("order"));
logger.info("---用戶已收貨,訂單完成---");
}
首先椎木,receive方法的參數(shù)由之前的為空:
public void receive() {
logger.info("---用戶已收貨违柏,訂單完成---");
}
?? 改成了Message<OrderEvents> message,這樣就能從message的getHeaders里面取到傳遞過來的數(shù)據(jù)對象了香椎。
?? 另外如果我們需要傳遞多個數(shù)據(jù)對象怎么辦呢漱竖,比如我們在實際業(yè)務(wù)中,除了傳訂單數(shù)據(jù)畜伐,可能還需要把商品數(shù)據(jù)馍惹,或者支付結(jié)果數(shù)據(jù)也傳過來,那么也容易玛界,我們還是從controller里面開始:
Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).setHeader("otherobj", "otherobjvalue").build();
在后面繼續(xù)setHeader就好了万矾,然后到eventConfig里面:
System.out.println("傳遞的參數(shù):" + message.getHeaders().get("order"));
System.out.println("傳遞的參數(shù):" + message.getHeaders().get("otherObj"));
運行后看日志:
傳遞的參數(shù):Order [id=null, userId=547568678, address=廣東省深圳市, phoneNum=13435465465, state=RECEIVE]
傳遞的參數(shù):otherObjValue
?? 可知兩個的數(shù)據(jù)都傳遞到了eventConfig里面了,這個就實現(xiàn)了多個數(shù)據(jù)對象的同時傳遞慎框。
?? 到這里為止良狈,狀態(tài)機通過message對象就和其他的業(yè)務(wù)代碼做到了數(shù)據(jù)連接。其實這個很關(guān)鍵笨枯,只有做到和其他業(yè)務(wù)的數(shù)據(jù)傳遞薪丁,才能算的上真正的可用遇西。
2.持久化
?? 目前為止,我們都是從狀態(tài)流程的開始階段創(chuàng)建一個狀態(tài)機严嗜,然后一路走下去粱檀。但在實際業(yè)務(wù)中,狀態(tài)機可能需要在某個環(huán)節(jié)停留漫玄,等待其他業(yè)務(wù)的觸發(fā)茄蚯,然后再繼續(xù)下面的流程。比如訂單称近,可能在支付環(huán)節(jié)需要等待一個剁手的用戶隔天再下單第队,所以這里面涉及到一個創(chuàng)建的狀態(tài)機該何去何從的問題。在spring statemachine中刨秆,給出來的辦法就是保存起來凳谦,到需要的時候取出來用。
1衡未、持久化到本地內(nèi)存
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)機
*/
@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 實現(xiàn)的接口是
org.springframework.statemachine.StateMachinePersist
,但在PersistConfig 里面抗俄,getPersister()方法返回的值類型是StateMachinePersister類型脆丁,看著很像,但并不是上面的這個接口动雹,而是org.springframework.statemachine.persist.StateMachinePersister
接口槽卫,為了表示這個坑對我的傷害,我要強調(diào)一下兩個接口:
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對象的躲庄,所以我們需要先實現(xiàn)上面的StateMachinePersist,然后再一個Config類里面轉(zhuǎn)換成下面的StateMachinePersister钾虐,轉(zhuǎn)換的代碼就在上面的PersistConfig類里读跷。
然后我們就能在controller里面使用了
@Resource(name="orderMemoryPersister")
private StateMachinePersister<OrderStates, OrderEvents, String> orderMemorypersister;
......
//保存狀態(tài)機
@RequestMapping("/testMemoryPersister")
public void tesMemorytPersister(String id) throws Exception {
StateMachine<OrderStates, OrderEvents> stateMachine = 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)機
@RequestMapping("/testMemoryPersisterRestore")
public void testMemoryRestore(String id) throws Exception {
StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
orderMemorypersister.restore(stateMachine, id);
System.out.println("恢復(fù)狀態(tài)機后的狀態(tài)為:" + stateMachine.getState().getId());
}
2、持久化到redis
??真正的業(yè)務(wù)中禾唁,一般都是多臺機分布式運行效览,所以如果狀態(tài)機只能保存在本地內(nèi)容无切,就不能用在分布式應(yīng)用上了。spring提供了一個方便的辦法丐枉,使用redis解決這個問題哆键。讓我們看看怎么弄。
?? pom文件引入spring-statemachine-redis
<!-- redis持久化狀態(tài)機 -->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-redis</artifactId>
<version>1.2.9.RELEASE</version>
</dependency>
在springboot配置文件里面加上redis參數(shù)瘦锹,我這是application.properties
# REDIS (RedisProperties)
# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=localhost
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負值表示沒有限制)
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開啟并能用籍嘹,我們繼續(xù)⊥湓海回到我們熟悉的PersistConfig
@Configuration
public class PersistConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 注入RedisStateMachinePersister對象
*
* @return
*/
@Bean(name = "orderRedisPersister")
public RedisStateMachinePersister<OrderStates, OrderEvents> redisPersister() {
return new RedisStateMachinePersister<>(redisPersist());
}
/**
* 通過redisConnectionFactory創(chuàng)建StateMachinePersist
*
* @return
*/
public StateMachinePersist<OrderStates, OrderEvents,String> redisPersist() {
RedisStateMachineContextRepository<OrderStates, OrderEvents> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
return new RepositoryStateMachinePersist<>(repository);
}
}
這個套路和上面保存到本地內(nèi)存是一樣一樣的辱士,先生成一個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);
//發(fā)送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("恢復(fù)狀態(tài)機后的狀態(tài)為:" + stateMachine.getState().getId());
}
執(zhí)行完redis保存statemachine后椅挣,大家可以自己在redis客戶端查看以下头岔,是不是有內(nèi)容保存進去了。
3.偽持久化和中間段的狀態(tài)機
??我們設(shè)想一個業(yè)務(wù)場景鼠证,就比如訂單吧峡竣,我們一般的設(shè)計都會把訂單狀態(tài)存到訂單表里面,其他的業(yè)務(wù)信息也都有表保存量九,而狀態(tài)機的主要作用其實是規(guī)范整個訂單業(yè)務(wù)流程的狀態(tài)和事件适掰,所以狀態(tài)機要不要保存真的不重要,我們只需要從訂單表里面把狀態(tài)取出來荠列,知道當(dāng)前是什么狀態(tài)攻谁,然后伴隨著業(yè)務(wù)繼續(xù)流浪到下一個狀態(tài)節(jié)點就好了。我們先實現(xiàn)一個StateMachinePersist弯予,因為我不想真的持久化,所以就敷衍一下个曙,持久化是什么锈嫩,啥也不干。
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Component;
@Component
public class OrderStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, Order> {
@Override
public void write(StateMachineContext<OrderStates, OrderEvents> context, Order contextObj) throws Exception {
//這里不做任何持久化工作
}
@Override
public StateMachineContext<OrderStates, OrderEvents> read(Order contextObj) throws Exception {
StateMachineContext<OrderStates, OrderEvents> result = new DefaultStateMachineContext<OrderStates, OrderEvents>(OrderStates.valueOf(contextObj.getState()),
null, null, null, null, "orderMachine");
return result;
}
}
然后在PersistConfig里面轉(zhuǎn)換成StateMachinePersister
@Configuration
public class PersistConfig {
@Autowired
private OrderStateMachinePersist orderStateMachinePersist;
@Bean(name="orderPersister")
public StateMachinePersister<OrderStates, OrderEvents, Order> orderPersister() {
return new DefaultStateMachinePersister<OrderStates, OrderEvents, Order>(orderStateMachinePersist);
}
}
現(xiàn)在問題來了垦搬,不持久化的持久化類是為啥呢呼寸,主要就是為了取一個任何狀態(tài)節(jié)點的狀態(tài)機,方便繼續(xù)往下執(zhí)行猴贰,請看controller
@RestController
@RequestMapping("/statemachine")
public class StateMachineController {
@Resource(name="orderPersister")
private StateMachinePersister<OrderStates, OrderEvents, Order> persister;
@RequestMapping("/testOrderRestore")
public void testOrderRestore(String id) throws Exception {
StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
//訂單
Order order = new Order();
order.setId(id);
order.setState(OrderStates.WAITING_FOR_RECEIVE.toString());
//恢復(fù)
persister.restore(stateMachine, order);
//查看恢復(fù)后狀態(tài)機的狀態(tài)
System.out.println("恢復(fù)后的狀態(tài):" + stateMachine.getState().getId());
}
}
??看到?jīng)]有对雪,用builder建了一個新的狀態(tài)機,用restore過了一手米绕,就已經(jīng)是一個到達order指定狀態(tài)的老司機狀態(tài)機了瑟捣,在這里馋艺,持久化不是本意,讓狀態(tài)機能夠隨時抓換到任意狀態(tài)節(jié)點才是目的迈套。在實際的企業(yè)開發(fā)中捐祠,不可能所有情況都是從頭到尾的按狀態(tài)流程來,會有很多意外桑李,比如歷史數(shù)據(jù)踱蛀,故障重啟后的遺留流程......,所以這種可以任意調(diào)節(jié)狀態(tài)的才是我們需要的狀態(tài)機贵白。