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 。
狀態(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)為例
①現(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(StateMachineStateConfigurer states)throwsException{ states .withStates() .initial(OrderStatus.WAIT_PAYMENT) .states(EnumSet.allOf(OrderStatus.class)); } /** *配置狀態(tài)轉(zhuǎn)換事件關(guān)系 * *@paramtransitions *@throwsException */ publicvoidconfigure(StateMachineTransitionConfigurer transitions)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") publicRedisStateMachinePersister getRedisPersister(){ RedisStateMachineContextRepository repository=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 privateStateMachine orderStateMachine; @Resource privateStateMachinePersister stateMachineMemPersister; @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(Message message){ 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(Message message){ 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
報錯如下:
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(Message message){ 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í)行完之后才返回
監(jiān)聽線程:
解決方案:自己保存異常到數(shù)據(jù)庫或者內(nèi)存中,進行判斷
也可以通過接口:org.springframework.statemachine.StateMachine##getExtendedState
方法把執(zhí)行狀態(tài)放入這個變量中
publicinterfaceExtendedState{ Map
org.springframework.statemachine.support.DefaultExtendedState##getVariables
privatefinalMap
改造監(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(Message message){ 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 privateStateMachinePersister stateMachineMemPersister; /** *發(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 privateStateMachinePersister stateMachineMemPersister; @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(Message message){ 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(Message message){ Orderorder=(Order)message.getHeaders().get("order"); log.info("確認收貨,狀態(tài)機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.FINISH.getKey()); orderMapper.updateById(order); //TODO其他業(yè)務(wù) } }
-
編程
+關(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)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論