0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Spring狀態(tài)機存在的問題

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-05-22 11:12 ? 次閱讀

1、什么是狀態(tài)機

1.1 什么是狀態(tài)

1.2 四大概念

1.3 狀態(tài)機

2、狀態(tài)機圖

3、spring statemachine

3.1 狀態(tài)機spring statemachine 概述

3.2 快速開始

3.3 測試驗證

3.4 狀態(tài)機存在的問題

1、什么是狀態(tài)機

1.1 什么是狀態(tài)

先來解釋什么是“狀態(tài)”( State )?,F(xiàn)實事物是有不同狀態(tài)的,例如一個自動門,就有 open 和 closed 兩種狀態(tài)。我們通常所說的狀態(tài)機是有限狀態(tài)機,也就是被描述的事物的狀態(tài)的數(shù)量是有限個,例如自動門的狀態(tài)就是兩個 open 和 closed 。

eeff0844-f84b-11ed-90ce-dac502259ad0.jpg

狀態(tài)機,也就是 State Machine ,不是指一臺實際機器,而是指一個數(shù)學模型。說白了,一般就是指一張狀態(tài)轉(zhuǎn)換圖。例如,根據(jù)自動門的運行規(guī)則,我們可以抽象出下面這么一個圖。

自動門有兩個狀態(tài),open 和 closed ,closed 狀態(tài)下,如果讀取開門信號,那么狀態(tài)就會切換為 open 。open 狀態(tài)下如果讀取關(guān)門信號,狀態(tài)就會切換為 closed 。

狀態(tài)機的全稱是有限狀態(tài)自動機,自動兩個字也是包含重要含義的。給定一個狀態(tài)機,同時給定它的當前狀態(tài)以及輸入,那么輸出狀態(tài)時可以明確的運算出來的。例如對于自動門,給定初始狀態(tài) closed ,給定輸入“開門”,那么下一個狀態(tài)時可以運算出來的。

這樣狀態(tài)機的基本定義我們就介紹完畢了。重復(fù)一下:狀態(tài)機是有限狀態(tài)自動機的簡稱,是現(xiàn)實事物運行規(guī)則抽象而成的一個數(shù)學模型。

1.2 四大概念

下面來給出狀態(tài)機的四大概念。

第一個是 State ,狀態(tài)。一個狀態(tài)機至少要包含兩個狀態(tài)。例如上面自動門的例子,有 open 和 closed 兩個狀態(tài)。

第二個是 Event ,事件。事件就是執(zhí)行某個操作的觸發(fā)條件或者口令。對于自動門,“按下開門按鈕”就是一個事件。

第三個是 Action ,動作。事件發(fā)生以后要執(zhí)行動作。例如事件是“按開門按鈕”,動作是“開門”。編程的時候,一個 Action一般就對應(yīng)一個函數(shù)。

第四個是 Transition ,變換。也就是從一個狀態(tài)變化為另一個狀態(tài)。例如“開門過程”就是一個變換。

1.3 狀態(tài)機

有限狀態(tài)機(Finite-state machine,FSM),又稱有限狀態(tài)自動機,簡稱狀態(tài)機,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學模型。

FSM是一種算法思想,簡單而言,有限狀態(tài)機由一組狀態(tài)、一個初始狀態(tài)、輸入和根據(jù)輸入及現(xiàn)有狀態(tài)轉(zhuǎn)換為下一個狀態(tài)的轉(zhuǎn)換函數(shù)組成。

其作用主要是描述對象在它的生命周期內(nèi)所經(jīng)歷的狀態(tài)序列,以及如何響應(yīng)來自外界的各種事件。

Java指南:https://java-family.cn

基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/ruoyi-vue-pro

視頻教程:https://doc.iocoder.cn/video/

2、狀態(tài)機圖

做需求時,需要了解以下六種元素:起始、終止、現(xiàn)態(tài)、次態(tài)(目標狀態(tài))、動作、條件,我們就可以完成一個狀態(tài)機圖了:

以訂單為例:以從待支付狀態(tài)轉(zhuǎn)換為待發(fā)貨狀態(tài)為例

ef0affbe-f84b-11ed-90ce-dac502259ad0.jpg

①現(xiàn)態(tài):是指當前所處的狀態(tài)。待支付

②條件:又稱為“事件”,當一個條件被滿足,將會觸發(fā)一個動作,或者執(zhí)行一次狀態(tài)的遷移。支付事件

③動作:條件滿足后執(zhí)行的動作。動作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。動作不是必需的,當條件滿足后,也可以不執(zhí)行任何動作,直接遷移到新狀態(tài)。狀態(tài)轉(zhuǎn)換為待發(fā)貨

④次態(tài):條件滿足后要遷往的新狀態(tài)。“次態(tài)”是相對于“現(xiàn)態(tài)”而言的,“次態(tài)”一旦被激活,就轉(zhuǎn)變成新的“現(xiàn)態(tài)”了。待發(fā)貨 注意事項

1、避免把某個“程序動作”當作是一種“狀態(tài)”來處理。那么如何區(qū)分“動作”和“狀態(tài)”?“動作”是不穩(wěn)定的,即使沒有條件的觸發(fā),“動作”一旦執(zhí)行完畢就結(jié)束了;而“狀態(tài)”是相對穩(wěn)定的,如果沒有外部條件的觸發(fā),一個狀態(tài)會一直持續(xù)下去。關(guān)注工眾號:碼猿技術(shù)專欄,回復(fù)關(guān)鍵詞:1111 獲取阿里內(nèi)部Java性能調(diào)優(yōu)手冊!

2、狀態(tài)劃分時漏掉一些狀態(tài),導(dǎo)致跳轉(zhuǎn)邏輯不完整。所以在設(shè)計狀態(tài)機時,我們需要反復(fù)的查看設(shè)計的狀態(tài)圖或者狀態(tài)表,最終達到一種牢不可破的設(shè)計方案。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/yudao-cloud

視頻教程:https://doc.iocoder.cn/video/

3、spring statemachine

3.1 狀態(tài)機spring statemachine 概述

Spring Statemachine是應(yīng)用程序開發(fā)人員在Spring應(yīng)用程序中使用狀態(tài)機概念的框架

Spring Statemachine旨在提供以下功能:

易于使用的扁平單級狀態(tài)機,用于簡單的使用案例。

分層狀態(tài)機結(jié)構(gòu),以簡化復(fù)雜的狀態(tài)配置。

狀態(tài)機區(qū)域提供更復(fù)雜的狀態(tài)配置。

使用觸發(fā)器,轉(zhuǎn)換,警衛(wèi)和操作。

鍵入安全配置適配器。

生成器模式,用于在Spring Application上下文之外使用的簡單實例化通常用例的食譜

基于Zookeeper的分布式狀態(tài)機

狀態(tài)機事件監(jiān)聽器。

UML Eclipse Papyrus建模。

將計算機配置存儲在永久存儲中。

Spring IOC集成將bean與狀態(tài)機關(guān)聯(lián)起來。

狀態(tài)機功能強大,因為行為始終保證一致,使調(diào)試相對容易。這是因為操作規(guī)則是在機器啟動時寫成的。這個想法是你的應(yīng)用程序可能存在于有限數(shù)量的狀態(tài)中,某些預(yù)定義的觸發(fā)器可以將你的應(yīng)用程序從一個狀態(tài)轉(zhuǎn)移到另一個狀態(tài)。此類觸發(fā)器可以基于事件或計時器。

在應(yīng)用程序之外定義高級邏輯然后依靠狀態(tài)機來管理狀態(tài)要容易得多。您可以通過發(fā)送事件,偵聽更改或僅請求當前狀態(tài)來與狀態(tài)機進行交互。

官網(wǎng):spring.io/projects/sp…

3.2 快速開始

以訂單狀態(tài)扭轉(zhuǎn)的例子為例:

表結(jié)構(gòu)設(shè)計如下:

CREATETABLE`tb_order`(
`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'主鍵ID',
`order_code`varchar(128)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'訂單編碼',
`status`smallint(3)DEFAULTNULLCOMMENT'訂單狀態(tài)',
`name`varchar(64)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'訂單名稱',
`price`decimal(12,2)DEFAULTNULLCOMMENT'價格',
`delete_flag`tinyint(2)NOTNULLDEFAULT'0'COMMENT'刪除標記,0未刪除1已刪除',
`create_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'創(chuàng)建時間',
`update_time`timestampNOTNULLDEFAULT'0000-00-000000'COMMENT'更新時間',
`create_user_code`varchar(32)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'創(chuàng)建人',
`update_user_code`varchar(32)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'更新人',
`version`int(11)NOTNULLDEFAULT'0'COMMENT'版本號',
`remark`varchar(64)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'備注',
PRIMARYKEY(`id`)
)ENGINE=InnoDBAUTO_INCREMENT=6DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_binCOMMENT='訂單表';

/*Dataforthetable`tb_order`*/

insertinto`tb_order`(`id`,`order_code`,`status`,`name`,`price`,`delete_flag`,`create_time`,`update_time`,`create_user_code`,`update_user_code`,`version`,`remark`)values
(2,'A111',1,'A','22.00',0,'2022-10-151611','2022-10-022114','zhangsan','zhangsan',0,NULL),
(3,'A111',1,'訂單A','22.00',0,'2022-10-022113','2022-10-022114','zhangsan','zhangsan',0,NULL),
(4,'A111',1,'訂單A','22.00',0,'2022-10-022113','2022-10-022114','zhangsan','zhangsan',0,NULL),
(5,'A111',1,'訂單A','22.00',0,'2022-10-030930','2022-10-022114','zhangsan','zhangsan',0,NULL);

1)引入依賴

 

org.springframework.statemachine
spring-statemachine-redis
1.2.9.RELEASE

 

org.springframework.statemachine
spring-statemachine-starter
2.0.1.RELEASE

2)定義狀態(tài)機狀態(tài)和事件

狀態(tài)枚舉:

/**
*author:芋道源碼
*/
publicenumOrderStatus{
//待支付,待發(fā)貨,待收貨,已完成
WAIT_PAYMENT(1,"待支付"),
WAIT_DELIVER(2,"待發(fā)貨"),
WAIT_RECEIVE(3,"待收貨"),
FINISH(4,"已完成");
privateIntegerkey;
privateStringdesc;
OrderStatus(Integerkey,Stringdesc){
this.key=key;
this.desc=desc;
}
publicIntegergetKey(){
returnkey;
}
publicStringgetDesc(){
returndesc;
}
publicstaticOrderStatusgetByKey(Integerkey){
for(OrderStatuse:values()){
if(e.getKey().equals(key)){
returne;
}
}
thrownewRuntimeException("enumnotexists.");
}
}

事件:

/**
*author:芋道源碼
*/
publicenumOrderStatusChangeEvent{
//支付,發(fā)貨,確認收貨
PAYED,DELIVERY,RECEIVED;
}

3)定義狀態(tài)機規(guī)則和配置狀態(tài)機

@Configuration
@EnableStateMachine(name="orderStateMachine")
publicclassOrderStateMachineConfigextendsStateMachineConfigurerAdapter{
/**
*配置狀態(tài)
*
*@paramstates
*@throwsException
*/
publicvoidconfigure(StateMachineStateConfigurerstates)throwsException{
states
.withStates()
.initial(OrderStatus.WAIT_PAYMENT)
.states(EnumSet.allOf(OrderStatus.class));
}
/**
*配置狀態(tài)轉(zhuǎn)換事件關(guān)系
*
*@paramtransitions
*@throwsException
*/
publicvoidconfigure(StateMachineTransitionConfigurertransitions)throwsException{
transitions
//支付事件:待支付-》待發(fā)貨
.withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
.and()
//發(fā)貨事件:待發(fā)貨-》待收貨
.withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
.and()
//收貨事件:待收貨-》已完成
.withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
}
}

配置持久化:

/**
*author:芋道源碼
*/
@Configuration
@Slf4j
publicclassPersist{
/**
*持久化到內(nèi)存map中
*
*@return
*/
@Bean(name="stateMachineMemPersister")
publicstaticStateMachinePersistergetPersister(){
returnnewDefaultStateMachinePersister(newStateMachinePersist(){
@Override
publicvoidwrite(StateMachineContextcontext,ObjectcontextObj)throwsException{
log.info("持久化狀態(tài)機,context:{},contextObj:{}",JSON.toJSONString(context),JSON.toJSONString(contextObj));
map.put(contextObj,context);
}
@Override
publicStateMachineContextread(ObjectcontextObj)throwsException{
log.info("獲取狀態(tài)機,contextObj:{}",JSON.toJSONString(contextObj));
StateMachineContextstateMachineContext=(StateMachineContext)map.get(contextObj);
log.info("獲取狀態(tài)機結(jié)果,stateMachineContext:{}",JSON.toJSONString(stateMachineContext));
returnstateMachineContext;
}
privateMapmap=newHashMap();
});
}

@Resource
privateRedisConnectionFactoryredisConnectionFactory;
/**
*持久化到redis中,在分布式系統(tǒng)中使用
*
*@return
*/
@Bean(name="stateMachineRedisPersister")
publicRedisStateMachinePersistergetRedisPersister(){
RedisStateMachineContextRepositoryrepository=newRedisStateMachineContextRepository<>(redisConnectionFactory);
RepositoryStateMachinePersistp=newRepositoryStateMachinePersist<>(repository);
returnnewRedisStateMachinePersister<>(p);
}
}

4)業(yè)務(wù)系統(tǒng)

controller:

/**
*author:芋道源碼
*/
@RestController
@RequestMapping("/order")
publicclassOrderController{
@Resource
privateOrderServiceorderService;
/**
*根據(jù)id查詢訂單
*
*@return
*/
@RequestMapping("/getById")
publicOrdergetById(@RequestParam("id")Longid){
//根據(jù)id查詢訂單
Orderorder=orderService.getById(id);
returnorder;
}
/**
*創(chuàng)建訂單
*
*@return
*/
@RequestMapping("/create")
publicStringcreate(@RequestBodyOrderorder){
//創(chuàng)建訂單
orderService.create(order);
return"sucess";
}
/**
*對訂單進行支付
*
*@paramid
*@return
*/
@RequestMapping("/pay")
publicStringpay(@RequestParam("id")Longid){
//對訂單進行支付
orderService.pay(id);
return"success";
}

/**
*對訂單進行發(fā)貨
*
*@paramid
*@return
*/
@RequestMapping("/deliver")
publicStringdeliver(@RequestParam("id")Longid){
//對訂單進行確認收貨
orderService.deliver(id);
return"success";
}
/**
*對訂單進行確認收貨
*
*@paramid
*@return
*/
@RequestMapping("/receive")
publicStringreceive(@RequestParam("id")Longid){
//對訂單進行確認收貨
orderService.receive(id);
return"success";
}
}

servie:

/**
*author:芋道源碼
*/
@Service("orderService")
@Slf4j
publicclassOrderServiceImplextendsServiceImplimplementsOrderService{
@Resource
privateStateMachineorderStateMachine;
@Resource
privateStateMachinePersisterstateMachineMemPersister;
@Resource
privateOrderMapperorderMapper;
/**
*創(chuàng)建訂單
*
*@paramorder
*@return
*/
publicOrdercreate(Orderorder){
order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());
orderMapper.insert(order);
returnorder;
}
/**
*對訂單進行支付
*
*@paramid
*@return
*/
publicOrderpay(Longid){
Orderorder=orderMapper.selectById(id);
log.info("線程名稱:{},嘗試支付,訂單號:{}",Thread.currentThread().getName(),id);
if(!sendEvent(OrderStatusChangeEvent.PAYED,order)){
log.error("線程名稱:{},支付失敗,狀態(tài)異常,訂單信息:{}",Thread.currentThread().getName(),order);
thrownewRuntimeException("支付失敗,訂單狀態(tài)異常");
}
returnorder;
}
/**
*對訂單進行發(fā)貨
*
*@paramid
*@return
*/
publicOrderdeliver(Longid){
Orderorder=orderMapper.selectById(id);
log.info("線程名稱:{},嘗試發(fā)貨,訂單號:{}",Thread.currentThread().getName(),id);
if(!sendEvent(OrderStatusChangeEvent.DELIVERY,order)){
log.error("線程名稱:{},發(fā)貨失敗,狀態(tài)異常,訂單信息:{}",Thread.currentThread().getName(),order);
thrownewRuntimeException("發(fā)貨失敗,訂單狀態(tài)異常");
}
returnorder;
}
/**
*對訂單進行確認收貨
*
*@paramid
*@return
*/
publicOrderreceive(Longid){
Orderorder=orderMapper.selectById(id);
log.info("線程名稱:{},嘗試收貨,訂單號:{}",Thread.currentThread().getName(),id);
if(!sendEvent(OrderStatusChangeEvent.RECEIVED,order)){
log.error("線程名稱:{},收貨失敗,狀態(tài)異常,訂單信息:{}",Thread.currentThread().getName(),order);
thrownewRuntimeException("收貨失敗,訂單狀態(tài)異常");
}
returnorder;
}
/**
*發(fā)送訂單狀態(tài)轉(zhuǎn)換事件
*synchronized修飾保證這個方法是線程安全的
*
*@paramchangeEvent
*@paramorder
*@return
*/
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){
booleanresult=false;
try{
//啟動狀態(tài)機
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機狀態(tài)
stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId()));
Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build();
result=orderStateMachine.sendEvent(message);
//持久化狀態(tài)機狀態(tài)
stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId()));
}catch(Exceptione){
log.error("訂單操作失敗:{}",e);
}finally{
orderStateMachine.stop();
}
returnresult;
}
}

監(jiān)聽狀態(tài)的變化:

/**
*author:芋道源碼
*/
@Component("orderStateListener")
@WithStateMachine(name="orderStateMachine")
@Slf4j
publicclassOrderStateListenerImpl{
@Resource
privateOrderMapperorderMapper;

@OnTransition(source="WAIT_PAYMENT",target="WAIT_DELIVER")
publicvoidpayTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("支付,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
//更新訂單
order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
}
@OnTransition(source="WAIT_DELIVER",target="WAIT_RECEIVE")
publicvoiddeliverTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("發(fā)貨,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
//更新訂單
order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
}
@OnTransition(source="WAIT_RECEIVE",target="FINISH")
publicvoidreceiveTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("確認收貨,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
//更新訂單
order.setStatus(OrderStatus.FINISH.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
}
}

3.3 測試驗證

1)驗證業(yè)務(wù)

新增一個訂單

http://localhost:8084/order/create

對訂單進行支付

http://localhost:8084/order/pay?id=2

對訂單進行發(fā)貨

http://localhost:8084/order/deliver?id=2

對訂單進行確認收貨

http://localhost:8084/order/receive?id=2

正常流程結(jié)束。如果對一個訂單進行支付了,再次進行支付,則會報錯:http://localhost:8084/order/pay?id=2

報錯如下:

ef14c922-f84b-11ed-90ce-dac502259ad0.jpg

2)驗證持久化

內(nèi)存

使用內(nèi)存持久化類持久化:

/**
*author:芋道源碼
*/
@Resource
privateStateMachinePersisterstateMachineMemPersister;

/**
*發(fā)送訂單狀態(tài)轉(zhuǎn)換事件
*synchronized修飾保證這個方法是線程安全的
*
*@paramchangeEvent
*@paramorder
*@return
*/
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){
booleanresult=false;
try{
//啟動狀態(tài)機
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機狀態(tài)
stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId()));
Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build();
result=orderStateMachine.sendEvent(message);
//持久化狀態(tài)機狀態(tài)
stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId()));
}catch(Exceptione){
log.error("訂單操作失敗:{}",e);
}finally{
orderStateMachine.stop();
}
returnresult;
}

redis持久化

引入依賴:

 

org.springframework.statemachine
spring-statemachine-redis
1.2.9.RELEASE

配置yaml:

spring:
redis:
database:0
host:localhost
jedis:
pool:
max-active:8
max-idle:8
max-wait:''
min-idle:0
password:''
port:6379
timeout:0

使用redis持久化類持久化:

/**
*author:芋道源碼
*/
@Resource
privateStateMachinePersisterstateMachineRedisPersister;

/**
*發(fā)送訂單狀態(tài)轉(zhuǎn)換事件
*synchronized修飾保證這個方法是線程安全的
*
*@paramchangeEvent
*@paramorder
*@return
*/
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){
booleanresult=false;
try{
//啟動狀態(tài)機
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機狀態(tài)
stateMachineRedisPersister.restore(orderStateMachine,String.valueOf(order.getId()));
Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build();
result=orderStateMachine.sendEvent(message);
//持久化狀態(tài)機狀態(tài)
stateMachineRedisPersister.persist(orderStateMachine,String.valueOf(order.getId()));
}catch(Exceptione){
log.error("訂單操作失敗:{}",e);
}finally{
orderStateMachine.stop();
}
returnresult;
}

3.4 狀態(tài)機存在的問題

1)stateMachine無法拋出異常,異常會被狀態(tài)機給消化掉

問題現(xiàn)象

從orderStateMachine.sendEvent(message);獲取的結(jié)果無法感知到。無論執(zhí)行正常還是拋出異常,都返回true。

@Resource
privateOrderMapperorderMapper;

@Resource
privateStateMachineorderStateMachine;

@OnTransition(source="WAIT_PAYMENT",target="WAIT_DELIVER")
@Transactional(rollbackFor=Exception.class)
publicvoidpayTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("支付,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
try{
//更新訂單
order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
//模擬異常
if(Objects.equals(order.getName(),"A")){
thrownewRuntimeException("執(zhí)行業(yè)務(wù)異常");
}
}catch(Exceptione){
//如果出現(xiàn)異常,記錄異常信息,拋出異常信息進行回滾
log.error("payTransition出現(xiàn)異常:{}",e);
throwe;
}
}

監(jiān)聽事件拋出異常,在發(fā)送事件中無法感知:

privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){
booleanresult=false;
try{
//啟動狀態(tài)機
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機狀態(tài)
stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId()));
Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build();
//事件執(zhí)行異常了,依然返回true,無法感知異常
result=orderStateMachine.sendEvent(message);
if(result){
//持久化狀態(tài)機狀態(tài),如果根據(jù)true持久化,則會出現(xiàn)問題
stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId()));
}
}catch(Exceptione){
log.error("訂單操作失敗:{}",e);
}finally{
orderStateMachine.stop();
}
returnresult;
}

調(diào)試發(fā)現(xiàn):發(fā)送事件和監(jiān)聽事件是一個線程,發(fā)送事件的結(jié)果是在監(jiān)聽操作執(zhí)行完之后才返回

ef1b945a-f84b-11ed-90ce-dac502259ad0.jpg

監(jiān)聽線程:

ef278292-f84b-11ed-90ce-dac502259ad0.jpg

解決方案:自己保存異常到數(shù)據(jù)庫或者內(nèi)存中,進行判斷

也可以通過接口:org.springframework.statemachine.StateMachine##getExtendedState

方法把執(zhí)行狀態(tài)放入這個變量中

publicinterfaceExtendedState{
MapgetVariables();
Tget(Objectvar1,Classvar2);
voidsetExtendedStateChangeListener(ExtendedState.ExtendedStateChangeListenervar1);
publicinterfaceExtendedStateChangeListener{
voidchanged(Objectvar1,Objectvar2);
}
}

org.springframework.statemachine.support.DefaultExtendedState##getVariables

privatefinalMapvariables;

publicDefaultExtendedState(){
this.variables=newObservableMap(newConcurrentHashMap(),newDefaultExtendedState.LocalMapChangeListener());
}

publicMapgetVariables(){
returnthis.variables;
}

改造監(jiān)聽狀態(tài):把業(yè)務(wù)的執(zhí)行結(jié)果進行保存,1成功,0失敗

@Resource
privateOrderMapperorderMapper;
@Resource
privateStateMachineorderStateMachine;

@OnTransition(source="WAIT_PAYMENT",target="WAIT_DELIVER")
@Transactional(rollbackFor=Exception.class)
publicvoidpayTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("支付,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
try{
//更新訂單
order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
//模擬異常
if(Objects.equals(order.getName(),"A")){
thrownewRuntimeException("執(zhí)行業(yè)務(wù)異常");
}
//成功則為1
orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
}catch(Exceptione){
//如果出現(xiàn)異常,則進行回滾
log.error("payTransition出現(xiàn)異常:{}",e);
//將異常信息變量信息中,失敗則為0
orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),0);
throwe;
}
}

發(fā)送事件改造:如果獲取到業(yè)務(wù)執(zhí)行異常,則返回失敗,不進行狀態(tài)機持久化 com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##sendEvent

@Resource
privateStateMachineorderStateMachine;
@Resource
privateStateMachinePersisterstateMachineMemPersister;

/**
*發(fā)送訂單狀態(tài)轉(zhuǎn)換事件
*synchronized修飾保證這個方法是線程安全的
*
*@paramchangeEvent
*@paramorder
*@return
*/
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){
booleanresult=false;
try{
//啟動狀態(tài)機
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機狀態(tài)
stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId()));
Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build();
result=orderStateMachine.sendEvent(message);
if(!result){
returnfalse;
}
//獲取到監(jiān)聽的結(jié)果信息
Integero=(Integer)orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition+order.getId());
//操作完成之后,刪除本次對應(yīng)的key信息
orderStateMachine.getExtendedState().getVariables().remove(CommonConstants.payTransition+order.getId());
//如果事務(wù)執(zhí)行成功,則持久化狀態(tài)機
if(Objects.equals(1,Integer.valueOf(o))){
//持久化狀態(tài)機狀態(tài)
stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId()));
}else{
//訂單執(zhí)行業(yè)務(wù)異常
returnfalse;
}
}catch(Exceptione){
log.error("訂單操作失敗:{}",e);
}finally{
orderStateMachine.stop();
}
returnresult;
}

代碼優(yōu)化

發(fā)送事件只針對了支付,如果是非支付事件呢?

//獲取到監(jiān)聽的結(jié)果信息
Integero=(Integer)orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition+order.getId());

監(jiān)聽設(shè)置狀態(tài)的代碼有重復(fù)代碼,需要進行優(yōu)化,可使用aop

try{
//TODO其他業(yè)務(wù)
//成功則為1
orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
}catch(Exceptione){
//如果出現(xiàn)異常,則進行回滾
log.error("payTransition出現(xiàn)異常:{}",e);
//將異常信息變量信息中,失敗則為0
orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),0);
throwe;
}

常量類:

publicinterfaceCommonConstants{
StringorderHeader="order";
StringpayTransition="payTransition";
StringdeliverTransition="deliverTransition";
StringreceiveTransition="receiveTransition";
}

支付發(fā)送事件:com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##pay

@Resource
privateStateMachineorderStateMachine;
@Resource
privateStateMachinePersisterstateMachineMemPersister;
@Resource
privateOrderMapperorderMapper;

/**
*對訂單進行支付
*
*@paramid
*@return
*/
publicOrderpay(Longid){
Orderorder=orderMapper.selectById(id);
log.info("線程名稱:{},嘗試支付,訂單號:{}",Thread.currentThread().getName(),id);
if(!sendEvent(OrderStatusChangeEvent.PAYED,order,CommonConstants.payTransition)){
log.error("線程名稱:{},支付失敗,狀態(tài)異常,訂單信息:{}",Thread.currentThread().getName(),order);
thrownewRuntimeException("支付失敗,訂單狀態(tài)異常");
}
returnorder;
}

/**
*發(fā)送訂單狀態(tài)轉(zhuǎn)換事件
*synchronized修飾保證這個方法是線程安全的
*
*@paramchangeEvent
*@paramorder
*@return
*/
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder,Stringkey){
booleanresult=false;
try{
//啟動狀態(tài)機
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機狀態(tài)
stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId()));
Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build();
result=orderStateMachine.sendEvent(message);
if(!result){
returnfalse;
}
//獲取到監(jiān)聽的結(jié)果信息
Integero=(Integer)orderStateMachine.getExtendedState().getVariables().get(key+order.getId());
//操作完成之后,刪除本次對應(yīng)的key信息
orderStateMachine.getExtendedState().getVariables().remove(key+order.getId());
//如果事務(wù)執(zhí)行成功,則持久化狀態(tài)機
if(Objects.equals(1,Integer.valueOf(o))){
//持久化狀態(tài)機狀態(tài)
stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId()));
}else{
//訂單執(zhí)行業(yè)務(wù)異常
returnfalse;
}
}catch(Exceptione){
log.error("訂單操作失敗:{}",e);
}finally{
orderStateMachine.stop();
}
returnresult;
}

使用aop對監(jiān)聽事件切面,把業(yè)務(wù)執(zhí)行結(jié)果封裝到狀態(tài)機的變量中,注解:

@Retention(RetentionPolicy.RUNTIME)
public@interfaceLogResult{
/**
*執(zhí)行的業(yè)務(wù)key
*
*@returnString
*/
Stringkey();
}

切面:

@Component
@Aspect
@Slf4j
publicclassLogResultAspect{

//攔截LogHistory注解
@Pointcut("@annotation(com.zengqingfa.springboot.state.demo.aop.annotation.LogResult)")
privatevoidlogResultPointCut(){
//logResultPointCut日志注解切點
}
@Resource
privateStateMachineorderStateMachine;

@Around("logResultPointCut()")
publicObjectlogResultAround(ProceedingJoinPointpjp)throwsThrowable{
//獲取參數(shù)
Object[]args=pjp.getArgs();
log.info("參數(shù)args:{}",args);
Messagemessage=(Message)args[0];
Orderorder=(Order)message.getHeaders().get("order");
//獲取方法
Methodmethod=((MethodSignature)pjp.getSignature()).getMethod();
//獲取LogHistory注解
LogResultlogResult=method.getAnnotation(LogResult.class);
Stringkey=logResult.key();
ObjectreturnVal=null;
try{
//執(zhí)行方法
returnVal=pjp.proceed();
//如果業(yè)務(wù)執(zhí)行正常,則保存信息
//成功則為1
orderStateMachine.getExtendedState().getVariables().put(key+order.getId(),1);
}catch(Throwablee){
log.error("e:{}",e.getMessage());
//如果業(yè)務(wù)執(zhí)行異常,則保存信息
//將異常信息變量信息中,失敗則為0
orderStateMachine.getExtendedState().getVariables().put(key+order.getId(),0);
throwe;
}
returnreturnVal;
}
}

監(jiān)聽類使用注解:

@Component("orderStateListener")
@WithStateMachine(name="orderStateMachine")
@Slf4j
publicclassOrderStateListenerImpl{
@Resource
privateOrderMapperorderMapper;

@OnTransition(source="WAIT_PAYMENT",target="WAIT_DELIVER")
@Transactional(rollbackFor=Exception.class)
@LogResult(key=CommonConstants.payTransition)
publicvoidpayTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("支付,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
//更新訂單
order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
//模擬異常
if(Objects.equals(order.getName(),"A")){
thrownewRuntimeException("執(zhí)行業(yè)務(wù)異常");
}
}
@OnTransition(source="WAIT_DELIVER",target="WAIT_RECEIVE")
@LogResult(key=CommonConstants.deliverTransition)
publicvoiddeliverTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("發(fā)貨,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
//更新訂單
order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
}
@OnTransition(source="WAIT_RECEIVE",target="FINISH")
@LogResult(key=CommonConstants.receiveTransition)
publicvoidreceiveTransition(Messagemessage){
Orderorder=(Order)message.getHeaders().get("order");
log.info("確認收貨,狀態(tài)機反饋信息:{}",message.getHeaders().toString());
//更新訂單
order.setStatus(OrderStatus.FINISH.getKey());
orderMapper.updateById(order);
//TODO其他業(yè)務(wù)
}
}
審核編輯:彭靜
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3565

    瀏覽量

    93536
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    338

    瀏覽量

    14295
  • 數(shù)學模型
    +關(guān)注

    關(guān)注

    0

    文章

    82

    瀏覽量

    11919
  • 自動機
    +關(guān)注

    關(guān)注

    1

    文章

    28

    瀏覽量

    9254

原文標題:項目終于用上了Spring狀態(tài)機,非常優(yōu)雅!

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Spring狀態(tài)機的實現(xiàn)原理和使用方法

    說起 Spring 狀態(tài)機,大家很容易聯(lián)想到這個狀態(tài)機和設(shè)計模式中狀態(tài)模式的區(qū)別是啥呢?沒錯,Spring
    的頭像 發(fā)表于 12-26 09:39 ?1834次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>狀態(tài)機</b>的實現(xiàn)原理和使用方法

    Verilog狀態(tài)機+設(shè)計實例

    在verilog中狀態(tài)機的一種很常用的邏輯結(jié)構(gòu),學習和理解狀態(tài)機的運行規(guī)律能夠幫助我們更好地書寫代碼,同時作為一種思想方法,在別的代碼設(shè)計中也會有所幫助。 一、簡介 在使用過程中我們常說
    的頭像 發(fā)表于 02-12 19:07 ?3676次閱讀
    Verilog<b class='flag-5'>狀態(tài)機</b>+設(shè)計實例

    玩轉(zhuǎn)Spring狀態(tài)機

    說起Spring狀態(tài)機,大家很容易聯(lián)想到這個狀態(tài)機和設(shè)計模式中狀態(tài)模式的區(qū)別是啥呢?沒錯,Spring
    的頭像 發(fā)表于 06-25 14:21 ?855次閱讀
    玩轉(zhuǎn)<b class='flag-5'>Spring</b><b class='flag-5'>狀態(tài)機</b>

    如何寫好狀態(tài)機

    如何寫好狀態(tài)機:狀態(tài)機是邏輯設(shè)計的重要內(nèi)容,狀態(tài)機的設(shè)計水平直接反應(yīng)工程師的邏輯功底,所以許多公司的硬件和邏輯工程師面試中,狀態(tài)機設(shè)計幾乎是必選題目。本章在引入
    發(fā)表于 06-14 19:24 ?97次下載

    狀態(tài)機舉例

    狀態(tài)機舉例 你可以指定狀態(tài)寄存器和狀態(tài)機狀態(tài)。以下是一個有四種狀態(tài)的普通狀態(tài)機。 // Th
    發(fā)表于 03-28 15:18 ?969次閱讀

    狀態(tài)機代碼生成工具

    狀態(tài)機代碼生成工具狀態(tài)機代碼生成工具狀態(tài)機代碼生成工具狀態(tài)機代碼生成工具
    發(fā)表于 11-19 15:12 ?9次下載

    狀態(tài)機原理及用法

    狀態(tài)機原理及用法狀態(tài)機原理及用法狀態(tài)機原理及用法
    發(fā)表于 03-15 15:25 ?0次下載

    簡述使用QII狀態(tài)機向?qū)绾蝿?chuàng)建一個狀態(tài)機

    如何使用QII狀態(tài)機向?qū)?chuàng)建一個狀態(tài)機
    的頭像 發(fā)表于 06-20 00:11 ?4184次閱讀
    簡述使用QII<b class='flag-5'>狀態(tài)機</b>向?qū)绾蝿?chuàng)建一個<b class='flag-5'>狀態(tài)機</b>

    狀態(tài)機概述 如何理解狀態(tài)機

    本篇文章包括狀態(tài)機的基本概述以及通過簡單的實例理解狀態(tài)機
    的頭像 發(fā)表于 01-02 18:03 ?1w次閱讀
    <b class='flag-5'>狀態(tài)機</b>概述  如何理解<b class='flag-5'>狀態(tài)機</b>

    什么是狀態(tài)機 狀態(tài)機的描述三種方法

    狀態(tài)機 1、狀態(tài)機是許多數(shù)字系統(tǒng)的核心部件,是一類重要的時序邏輯電路。通常包括三個部分:一是下一個狀態(tài)的邏輯電路,二是存儲狀態(tài)機當前狀態(tài)的時
    的頭像 發(fā)表于 11-16 17:39 ?2.7w次閱讀

    FPGA:狀態(tài)機簡述

    本文目錄 前言 狀態(tài)機簡介 狀態(tài)機分類 Mealy 型狀態(tài)機 Moore 型狀態(tài)機 狀態(tài)機描述 一段式
    的頭像 發(fā)表于 11-05 17:58 ?7263次閱讀
    FPGA:<b class='flag-5'>狀態(tài)機</b>簡述

    什么是狀態(tài)機狀態(tài)機5要素

    等。 本文來說一下狀態(tài)機編程。 什么是狀態(tài)機? 狀態(tài)機(state machine)有5個要素: 狀態(tài)(state) 遷移(transition) 事件(event) 動作(actio
    的頭像 發(fā)表于 07-27 11:23 ?2w次閱讀
    什么是<b class='flag-5'>狀態(tài)機</b>?<b class='flag-5'>狀態(tài)機</b>5要素

    狀態(tài)模式(狀態(tài)機)

    以前寫狀態(tài)機,比較常用的方式是用 if-else 或 switch-case,高級的一點是函數(shù)指針列表。最近,看了一文章《c語言設(shè)計模式–狀態(tài)模式(狀態(tài)機)》(來源:embed linux
    發(fā)表于 12-16 16:53 ?8次下載
    <b class='flag-5'>狀態(tài)</b>模式(<b class='flag-5'>狀態(tài)機</b>)

    labview狀態(tài)機分享

    labview狀態(tài)機
    發(fā)表于 10-31 15:50 ?13次下載

    什么是狀態(tài)機狀態(tài)機的種類與實現(xiàn)

    狀態(tài)機,又稱有限狀態(tài)機(Finite State Machine,F(xiàn)SM)或米利狀態(tài)機(Mealy Machine),是一種描述系統(tǒng)狀態(tài)變化的模型。在芯片設(shè)計中,
    的頭像 發(fā)表于 10-19 10:27 ?8842次閱讀