1、什么是狀態(tài)機(jī)
1.1 什么是狀態(tài)
1.2 四大概念
1.3 狀態(tài)機(jī)
2、狀態(tài)機(jī)圖
3、spring statemachine
3.1 狀態(tài)機(jī)spring statemachine 概述
3.2 快速開始
3.3 測試驗(yàn)證
3.4 狀態(tài)機(jī)存在的問題
1、什么是狀態(tài)機(jī)
1.1 什么是狀態(tài)
先來解釋什么是“狀態(tài)”( State )。現(xiàn)實(shí)事物是有不同狀態(tài)的,例如一個自動門,就有 open 和 closed 兩種狀態(tài)。我們通常所說的狀態(tài)機(jī)是有限狀態(tài)機(jī),也就是被描述的事物的狀態(tài)的數(shù)量是有限個,例如自動門的狀態(tài)就是兩個 open 和 closed 。
狀態(tài)機(jī),也就是 State Machine ,不是指一臺實(shí)際機(jī)器,而是指一個數(shù)學(xué)模型。說白了,一般就是指一張狀態(tài)轉(zhuǎn)換圖。例如,根據(jù)自動門的運(yùn)行規(guī)則,我們可以抽象出下面這么一個圖。
自動門有兩個狀態(tài),open 和 closed ,closed 狀態(tài)下,如果讀取開門信號,那么狀態(tài)就會切換為 open 。open 狀態(tài)下如果讀取關(guān)門信號,狀態(tài)就會切換為 closed 。
狀態(tài)機(jī)的全稱是有限狀態(tài)自動機(jī),自動兩個字也是包含重要含義的。給定一個狀態(tài)機(jī),同時(shí)給定它的當(dāng)前狀態(tài)以及輸入,那么輸出狀態(tài)時(shí)可以明確的運(yùn)算出來的。例如對于自動門,給定初始狀態(tài) closed ,給定輸入“開門”,那么下一個狀態(tài)時(shí)可以運(yùn)算出來的。
這樣狀態(tài)機(jī)的基本定義我們就介紹完畢了。重復(fù)一下:狀態(tài)機(jī)是有限狀態(tài)自動機(jī)的簡稱,是現(xiàn)實(shí)事物運(yùn)行規(guī)則抽象而成的一個數(shù)學(xué)模型。
1.2 四大概念
下面來給出狀態(tài)機(jī)的四大概念。
第一個是 State ,狀態(tài)。一個狀態(tài)機(jī)至少要包含兩個狀態(tài)。例如上面自動門的例子,有 open 和 closed 兩個狀態(tài)。
第二個是 Event ,事件。事件就是執(zhí)行某個操作的觸發(fā)條件或者口令。對于自動門,“按下開門按鈕”就是一個事件。
第三個是 Action ,動作。事件發(fā)生以后要執(zhí)行動作。例如事件是“按開門按鈕”,動作是“開門”。編程的時(shí)候,一個 Action一般就對應(yīng)一個函數(shù)。
第四個是 Transition ,變換。也就是從一個狀態(tài)變化為另一個狀態(tài)。例如“開門過程”就是一個變換。
1.3 狀態(tài)機(jī)
有限狀態(tài)機(jī)(Finite-state machine,FSM),又稱有限狀態(tài)自動機(jī),簡稱狀態(tài)機(jī),是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。
FSM是一種算法思想,簡單而言,有限狀態(tài)機(jī)由一組狀態(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 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
2、狀態(tài)機(jī)圖
做需求時(shí),需要了解以下六種元素:起始、終止、現(xiàn)態(tài)、次態(tài)(目標(biāo)狀態(tài))、動作、條件,我們就可以完成一個狀態(tài)機(jī)圖了:
以訂單為例:以從待支付狀態(tài)轉(zhuǎn)換為待發(fā)貨狀態(tài)為例
①現(xiàn)態(tài):是指當(dāng)前所處的狀態(tài)。待支付
②條件:又稱為“事件”,當(dāng)一個條件被滿足,將會觸發(fā)一個動作,或者執(zhí)行一次狀態(tài)的遷移。支付事件
③動作:條件滿足后執(zhí)行的動作。動作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。動作不是必需的,當(dāng)條件滿足后,也可以不執(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ā)貨 注意事項(xiàng)
1、避免把某個“程序動作”當(dāng)作是一種“狀態(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)劃分時(shí)漏掉一些狀態(tài),導(dǎo)致跳轉(zhuǎn)邏輯不完整。所以在設(shè)計(jì)狀態(tài)機(jī)時(shí),我們需要反復(fù)的查看設(shè)計(jì)的狀態(tài)圖或者狀態(tài)表,最終達(dá)到一種牢不可破的設(shè)計(jì)方案。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
3、spring statemachine
3.1 狀態(tài)機(jī)spring statemachine 概述
Spring Statemachine是應(yīng)用程序開發(fā)人員在Spring應(yīng)用程序中使用狀態(tài)機(jī)概念的框架
Spring Statemachine旨在提供以下功能:
易于使用的扁平單級狀態(tài)機(jī),用于簡單的使用案例。
分層狀態(tài)機(jī)結(jié)構(gòu),以簡化復(fù)雜的狀態(tài)配置。
狀態(tài)機(jī)區(qū)域提供更復(fù)雜的狀態(tài)配置。
使用觸發(fā)器,轉(zhuǎn)換,警衛(wèi)和操作。
鍵入安全配置適配器。
生成器模式,用于在Spring Application上下文之外使用的簡單實(shí)例化通常用例的食譜
基于Zookeeper的分布式狀態(tài)機(jī)
狀態(tài)機(jī)事件監(jiān)聽器。
UML Eclipse Papyrus建模。
將計(jì)算機(jī)配置存儲在永久存儲中。
Spring IOC集成將bean與狀態(tài)機(jī)關(guān)聯(lián)起來。
狀態(tài)機(jī)功能強(qiáng)大,因?yàn)樾袨槭冀K保證一致,使調(diào)試相對容易。這是因?yàn)椴僮饕?guī)則是在機(jī)器啟動時(shí)寫成的。這個想法是你的應(yīng)用程序可能存在于有限數(shù)量的狀態(tài)中,某些預(yù)定義的觸發(fā)器可以將你的應(yīng)用程序從一個狀態(tài)轉(zhuǎn)移到另一個狀態(tài)。此類觸發(fā)器可以基于事件或計(jì)時(shí)器。
在應(yīng)用程序之外定義高級邏輯然后依靠狀態(tài)機(jī)來管理狀態(tài)要容易得多。您可以通過發(fā)送事件,偵聽更改或僅請求當(dāng)前狀態(tài)來與狀態(tài)機(jī)進(jìn)行交互。
官網(wǎng):spring.io/projects/sp…
3.2 快速開始
以訂單狀態(tài)扭轉(zhuǎn)的例子為例:
表結(jié)構(gòu)設(shè)計(jì)如下:
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'價(jià)格', `delete_flag`tinyint(2)NOTNULLDEFAULT'0'COMMENT'刪除標(biāo)記,0未刪除1已刪除', `create_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'創(chuàng)建時(shí)間', `update_time`timestampNOTNULLDEFAULT'0000-00-000000'COMMENT'更新時(shí)間', `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)機(jī)狀態(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ā)貨,確認(rèn)收貨 PAYED,DELIVERY,RECEIVED; }
3)定義狀態(tài)機(jī)規(guī)則和配置狀態(tài)機(jī)
@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)機(jī),context:{},contextObj:{}",JSON.toJSONString(context),JSON.toJSONString(contextObj)); map.put(contextObj,context); } @Override publicStateMachineContextread(ObjectcontextObj)throwsException{ log.info("獲取狀態(tài)機(jī),contextObj:{}",JSON.toJSONString(contextObj)); StateMachineContextstateMachineContext=(StateMachineContext)map.get(contextObj); log.info("獲取狀態(tài)機(jī)結(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"; } /** *對訂單進(jìn)行支付 * *@paramid *@return */ @RequestMapping("/pay") publicStringpay(@RequestParam("id")Longid){ //對訂單進(jìn)行支付 orderService.pay(id); return"success"; } /** *對訂單進(jìn)行發(fā)貨 * *@paramid *@return */ @RequestMapping("/deliver") publicStringdeliver(@RequestParam("id")Longid){ //對訂單進(jìn)行確認(rèn)收貨 orderService.deliver(id); return"success"; } /** *對訂單進(jìn)行確認(rèn)收貨 * *@paramid *@return */ @RequestMapping("/receive") publicStringreceive(@RequestParam("id")Longid){ //對訂單進(jìn)行確認(rèn)收貨 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; } /** *對訂單進(jìn)行支付 * *@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; } /** *對訂單進(jìn)行發(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; } /** *對訂單進(jìn)行確認(rèn)收貨 * *@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)機(jī) orderStateMachine.start(); //嘗試恢復(fù)狀態(tài)機(jī)狀態(tài) stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); //持久化狀態(tài)機(jī)狀態(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)機(jī)反饋信息:{}",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)機(jī)反饋信息:{}",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("確認(rèn)收貨,狀態(tài)機(jī)反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.FINISH.getKey()); orderMapper.updateById(order); //TODO其他業(yè)務(wù) } }
3.3 測試驗(yàn)證
1)驗(yàn)證業(yè)務(wù)
新增一個訂單
http://localhost:8084/order/create
對訂單進(jìn)行支付
http://localhost:8084/order/pay?id=2
對訂單進(jìn)行發(fā)貨
http://localhost:8084/order/deliver?id=2
對訂單進(jìn)行確認(rèn)收貨
http://localhost:8084/order/receive?id=2
正常流程結(jié)束。如果對一個訂單進(jìn)行支付了,再次進(jìn)行支付,則會報(bào)錯:http://localhost:8084/order/pay?id=2
報(bào)錯如下:
2)驗(yàn)證持久化
內(nèi)存
使用內(nèi)存持久化類持久化:
/** *author:芋道源碼 */ @Resource privateStateMachinePersisterstateMachineMemPersister; /** *發(fā)送訂單狀態(tài)轉(zhuǎn)換事件 *synchronized修飾保證這個方法是線程安全的 * *@paramchangeEvent *@paramorder *@return */ privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態(tài)機(jī) orderStateMachine.start(); //嘗試恢復(fù)狀態(tài)機(jī)狀態(tài) stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); //持久化狀態(tài)機(jī)狀態(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)機(jī) orderStateMachine.start(); //嘗試恢復(fù)狀態(tài)機(jī)狀態(tài) stateMachineRedisPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); //持久化狀態(tài)機(jī)狀態(tài) stateMachineRedisPersister.persist(orderStateMachine,String.valueOf(order.getId())); }catch(Exceptione){ log.error("訂單操作失敗:{}",e); }finally{ orderStateMachine.stop(); } returnresult; }
3.4 狀態(tài)機(jī)存在的問題
1)stateMachine無法拋出異常,異常會被狀態(tài)機(jī)給消化掉
問題現(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)機(jī)反饋信息:{}",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)異常,記錄異常信息,拋出異常信息進(jìn)行回滾 log.error("payTransition出現(xiàn)異常:{}",e); throwe; } }
監(jiān)聽事件拋出異常,在發(fā)送事件中無法感知:
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態(tài)機(jī) orderStateMachine.start(); //嘗試恢復(fù)狀態(tài)機(jī)狀態(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)機(jī)狀態(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)存中,進(jìn)行判斷
也可以通過接口: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é)果進(jìn)行保存,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)機(jī)反饋信息:{}",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)異常,則進(jìn)行回滾 log.error("payTransition出現(xiàn)異常:{}",e); //將異常信息變量信息中,失敗則為0 orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),0); throwe; } }
發(fā)送事件改造:如果獲取到業(yè)務(wù)執(zhí)行異常,則返回失敗,不進(jìn)行狀態(tài)機(jī)持久化 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)機(jī) orderStateMachine.start(); //嘗試恢復(fù)狀態(tài)機(jī)狀態(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)機(jī) if(Objects.equals(1,Integer.valueOf(o))){ //持久化狀態(tài)機(jī)狀態(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ù)代碼,需要進(jìn)行優(yōu)化,可使用aop
try{ //TODO其他業(yè)務(wù) //成功則為1 orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1); }catch(Exceptione){ //如果出現(xiàn)異常,則進(jì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; /** *對訂單進(jìn)行支付 * *@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)機(jī) orderStateMachine.start(); //嘗試恢復(fù)狀態(tài)機(jī)狀態(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)機(jī) if(Objects.equals(1,Integer.valueOf(o))){ //持久化狀態(tài)機(jī)狀態(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)機(jī)的變量中,注解:
@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日志注解切點(diǎn) } @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)機(jī)反饋信息:{}",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)機(jī)反饋信息:{}",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("確認(rèn)收貨,狀態(tài)機(jī)反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.FINISH.getKey()); orderMapper.updateById(order); //TODO其他業(yè)務(wù) } }
-
編程
+關(guān)注
關(guān)注
88文章
3637瀏覽量
93992 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14392 -
數(shù)學(xué)模型
+關(guān)注
關(guān)注
0文章
83瀏覽量
11990 -
自動機(jī)
+關(guān)注
關(guān)注
1文章
28瀏覽量
9323
原文標(biāo)題:項(xiàng)目終于用上了Spring狀態(tài)機(jī),非常優(yōu)雅!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
Spring狀態(tài)機(jī)的實(shí)現(xiàn)原理和使用方法
![<b class='flag-5'>Spring</b><b class='flag-5'>狀態(tài)機(jī)</b>的實(shí)現(xiàn)原理和使用方法](https://file1.elecfans.com/web2/M00/B8/F8/wKgZomWKL0yAUurlAAAaFjoNwKM559.png)
Verilog狀態(tài)機(jī)+設(shè)計(jì)實(shí)例
![Verilog<b class='flag-5'>狀態(tài)機(jī)</b>+設(shè)計(jì)實(shí)例](https://file1.elecfans.com/web2/M00/C0/EA/wKgaomXI1-KAXkFgAAA1Fl4mqhA227.png)
玩轉(zhuǎn)Spring狀態(tài)機(jī)
![玩轉(zhuǎn)<b class='flag-5'>Spring</b><b class='flag-5'>狀態(tài)機(jī)</b>](https://file1.elecfans.com//web2/M00/F3/2C/wKgaomZ46Y6AHiVCAACxvB4H4AM171.png)
如何寫好狀態(tài)機(jī)
狀態(tài)機(jī)舉例
狀態(tài)機(jī)代碼生成工具
什么是狀態(tài)機(jī) 狀態(tài)機(jī)的描述三種方法
FPGA:狀態(tài)機(jī)簡述
![FPGA:<b class='flag-5'>狀態(tài)機(jī)</b>簡述](https://file.elecfans.com/web1/M00/CE/BE/pIYBAF-jzASAShR6AACiktqHlng534.png)
什么是狀態(tài)機(jī)?狀態(tài)機(jī)5要素
![什么是<b class='flag-5'>狀態(tài)機(jī)</b>?<b class='flag-5'>狀態(tài)機(jī)</b>5要素](https://file.elecfans.com/web2/M00/0C/18/poYBAGD_fReAI7mmAAAbpz2ipdw400.png)
狀態(tài)模式(狀態(tài)機(jī))
![<b class='flag-5'>狀態(tài)</b>模式(<b class='flag-5'>狀態(tài)機(jī)</b>)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論