1、什么是狀態機
1.1 什么是狀態
1.2 四大概念
1.3 狀態機
2、狀態機圖
3、spring statemachine
3.1 狀態機spring statemachine 概述
3.2 快速開始
3.3 測試驗證
3.4 狀態機存在的問題
1、什么是狀態機
1.1 什么是狀態
先來解釋什么是“狀態”( State )。現實事物是有不同狀態的,例如一個自動門,就有 open 和 closed 兩種狀態。我們通常所說的狀態機是有限狀態機,也就是被描述的事物的狀態的數量是有限個,例如自動門的狀態就是兩個 open 和 closed 。
狀態機,也就是 State Machine ,不是指一臺實際機器,而是指一個數學模型。說白了,一般就是指一張狀態轉換圖。例如,根據自動門的運行規則,我們可以抽象出下面這么一個圖。
自動門有兩個狀態,open 和 closed ,closed 狀態下,如果讀取開門信號,那么狀態就會切換為 open 。open 狀態下如果讀取關門信號,狀態就會切換為 closed 。
狀態機的全稱是有限狀態自動機,自動兩個字也是包含重要含義的。給定一個狀態機,同時給定它的當前狀態以及輸入,那么輸出狀態時可以明確的運算出來的。例如對于自動門,給定初始狀態 closed ,給定輸入“開門”,那么下一個狀態時可以運算出來的。
這樣狀態機的基本定義我們就介紹完畢了。重復一下:狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。
1.2 四大概念
下面來給出狀態機的四大概念。
第一個是 State ,狀態。一個狀態機至少要包含兩個狀態。例如上面自動門的例子,有 open 和 closed 兩個狀態。
第二個是 Event ,事件。事件就是執行某個操作的觸發條件或者口令。對于自動門,“按下開門按鈕”就是一個事件。
第三個是 Action ,動作。事件發生以后要執行動作。例如事件是“按開門按鈕”,動作是“開門”。編程的時候,一個 Action一般就對應一個函數。
第四個是 Transition ,變換。也就是從一個狀態變化為另一個狀態。例如“開門過程”就是一個變換。
1.3 狀態機
有限狀態機(Finite-state machine,FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。
FSM是一種算法思想,簡單而言,有限狀態機由一組狀態、一個初始狀態、輸入和根據輸入及現有狀態轉換為下一個狀態的轉換函數組成。
其作用主要是描述對象在它的生命周期內所經歷的狀態序列,以及如何響應來自外界的各種事件。
Java指南:https://java-family.cn
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
2、狀態機圖
做需求時,需要了解以下六種元素:起始、終止、現態、次態(目標狀態)、動作、條件,我們就可以完成一個狀態機圖了:
以訂單為例:以從待支付狀態轉換為待發貨狀態為例
①現態:是指當前所處的狀態。待支付
②條件:又稱為“事件”,當一個條件被滿足,將會觸發一個動作,或者執行一次狀態的遷移。支付事件
③動作:條件滿足后執行的動作。動作執行完畢后,可以遷移到新的狀態,也可以仍舊保持原狀態。動作不是必需的,當條件滿足后,也可以不執行任何動作,直接遷移到新狀態。狀態轉換為待發貨
④次態:條件滿足后要遷往的新狀態。“次態”是相對于“現態”而言的,“次態”一旦被激活,就轉變成新的“現態”了。待發貨 注意事項
1、避免把某個“程序動作”當作是一種“狀態”來處理。那么如何區分“動作”和“狀態”?“動作”是不穩定的,即使沒有條件的觸發,“動作”一旦執行完畢就結束了;而“狀態”是相對穩定的,如果沒有外部條件的觸發,一個狀態會一直持續下去。關注工眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部Java性能調優手冊!
2、狀態劃分時漏掉一些狀態,導致跳轉邏輯不完整。所以在設計狀態機時,我們需要反復的查看設計的狀態圖或者狀態表,最終達到一種牢不可破的設計方案。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
3、spring statemachine
3.1 狀態機spring statemachine 概述
Spring Statemachine是應用程序開發人員在Spring應用程序中使用狀態機概念的框架
Spring Statemachine旨在提供以下功能:
易于使用的扁平單級狀態機,用于簡單的使用案例。
分層狀態機結構,以簡化復雜的狀態配置。
狀態機區域提供更復雜的狀態配置。
使用觸發器,轉換,警衛和操作。
鍵入安全配置適配器。
生成器模式,用于在Spring Application上下文之外使用的簡單實例化通常用例的食譜
基于Zookeeper的分布式狀態機
狀態機事件監聽器。
UML Eclipse Papyrus建模。
將計算機配置存儲在永久存儲中。
Spring IOC集成將bean與狀態機關聯起來。
狀態機功能強大,因為行為始終保證一致,使調試相對容易。這是因為操作規則是在機器啟動時寫成的。這個想法是你的應用程序可能存在于有限數量的狀態中,某些預定義的觸發器可以將你的應用程序從一個狀態轉移到另一個狀態。此類觸發器可以基于事件或計時器。
在應用程序之外定義高級邏輯然后依靠狀態機來管理狀態要容易得多。您可以通過發送事件,偵聽更改或僅請求當前狀態來與狀態機進行交互。
官網:spring.io/projects/sp…
3.2 快速開始
以訂單狀態扭轉的例子為例:
表結構設計如下:
CREATETABLE`tb_order`( `id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'主鍵ID', `order_code`varchar(128)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'訂單編碼', `status`smallint(3)DEFAULTNULLCOMMENT'訂單狀態', `name`varchar(64)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'訂單名稱', `price`decimal(12,2)DEFAULTNULLCOMMENT'價格', `delete_flag`tinyint(2)NOTNULLDEFAULT'0'COMMENT'刪除標記,0未刪除1已刪除', `create_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'創建時間', `update_time`timestampNOTNULLDEFAULT'0000-00-000000'COMMENT'更新時間', `create_user_code`varchar(32)COLLATEutf8mb4_binDEFAULTNULLCOMMENT'創建人', `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)定義狀態機狀態和事件
狀態枚舉:
/** *author:芋道源碼 */ publicenumOrderStatus{ //待支付,待發貨,待收貨,已完成 WAIT_PAYMENT(1,"待支付"), WAIT_DELIVER(2,"待發貨"), 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{ //支付,發貨,確認收貨 PAYED,DELIVERY,RECEIVED; }
3)定義狀態機規則和配置狀態機
@Configuration @EnableStateMachine(name="orderStateMachine") publicclassOrderStateMachineConfigextendsStateMachineConfigurerAdapter{ /** *配置狀態 * *@paramstates *@throwsException */ publicvoidconfigure(StateMachineStateConfigurer states)throwsException{ states .withStates() .initial(OrderStatus.WAIT_PAYMENT) .states(EnumSet.allOf(OrderStatus.class)); } /** *配置狀態轉換事件關系 * *@paramtransitions *@throwsException */ publicvoidconfigure(StateMachineTransitionConfigurer transitions)throwsException{ transitions //支付事件:待支付-》待發貨 .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED) .and() //發貨事件:待發貨-》待收貨 .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{ /** *持久化到內存map中 * *@return */ @Bean(name="stateMachineMemPersister") publicstaticStateMachinePersistergetPersister(){ returnnewDefaultStateMachinePersister(newStateMachinePersist(){ @Override publicvoidwrite(StateMachineContextcontext,ObjectcontextObj)throwsException{ log.info("持久化狀態機,context:{},contextObj:{}",JSON.toJSONString(context),JSON.toJSONString(contextObj)); map.put(contextObj,context); } @Override publicStateMachineContextread(ObjectcontextObj)throwsException{ log.info("獲取狀態機,contextObj:{}",JSON.toJSONString(contextObj)); StateMachineContextstateMachineContext=(StateMachineContext)map.get(contextObj); log.info("獲取狀態機結果,stateMachineContext:{}",JSON.toJSONString(stateMachineContext)); returnstateMachineContext; } privateMapmap=newHashMap(); }); } @Resource privateRedisConnectionFactoryredisConnectionFactory; /** *持久化到redis中,在分布式系統中使用 * *@return */ @Bean(name="stateMachineRedisPersister") publicRedisStateMachinePersister getRedisPersister(){ RedisStateMachineContextRepository repository=newRedisStateMachineContextRepository<>(redisConnectionFactory); RepositoryStateMachinePersistp=newRepositoryStateMachinePersist<>(repository); returnnewRedisStateMachinePersister<>(p); } }
4)業務系統
controller:
/** *author:芋道源碼 */ @RestController @RequestMapping("/order") publicclassOrderController{ @Resource privateOrderServiceorderService; /** *根據id查詢訂單 * *@return */ @RequestMapping("/getById") publicOrdergetById(@RequestParam("id")Longid){ //根據id查詢訂單 Orderorder=orderService.getById(id); returnorder; } /** *創建訂單 * *@return */ @RequestMapping("/create") publicStringcreate(@RequestBodyOrderorder){ //創建訂單 orderService.create(order); return"sucess"; } /** *對訂單進行支付 * *@paramid *@return */ @RequestMapping("/pay") publicStringpay(@RequestParam("id")Longid){ //對訂單進行支付 orderService.pay(id); return"success"; } /** *對訂單進行發貨 * *@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; /** *創建訂單 * *@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("線程名稱:{},支付失敗,狀態異常,訂單信息:{}",Thread.currentThread().getName(),order); thrownewRuntimeException("支付失敗,訂單狀態異常"); } returnorder; } /** *對訂單進行發貨 * *@paramid *@return */ publicOrderdeliver(Longid){ Orderorder=orderMapper.selectById(id); log.info("線程名稱:{},嘗試發貨,訂單號:{}",Thread.currentThread().getName(),id); if(!sendEvent(OrderStatusChangeEvent.DELIVERY,order)){ log.error("線程名稱:{},發貨失敗,狀態異常,訂單信息:{}",Thread.currentThread().getName(),order); thrownewRuntimeException("發貨失敗,訂單狀態異常"); } returnorder; } /** *對訂單進行確認收貨 * *@paramid *@return */ publicOrderreceive(Longid){ Orderorder=orderMapper.selectById(id); log.info("線程名稱:{},嘗試收貨,訂單號:{}",Thread.currentThread().getName(),id); if(!sendEvent(OrderStatusChangeEvent.RECEIVED,order)){ log.error("線程名稱:{},收貨失敗,狀態異常,訂單信息:{}",Thread.currentThread().getName(),order); thrownewRuntimeException("收貨失敗,訂單狀態異常"); } returnorder; } /** *發送訂單狀態轉換事件 *synchronized修飾保證這個方法是線程安全的 * *@paramchangeEvent *@paramorder *@return */ privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態機 orderStateMachine.start(); //嘗試恢復狀態機狀態 stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); //持久化狀態機狀態 stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId())); }catch(Exceptione){ log.error("訂單操作失敗:{}",e); }finally{ orderStateMachine.stop(); } returnresult; } }
監聽狀態的變化:
/** *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("支付,狀態機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.WAIT_DELIVER.getKey()); orderMapper.updateById(order); //TODO其他業務 } @OnTransition(source="WAIT_DELIVER",target="WAIT_RECEIVE") publicvoiddeliverTransition(Message message){ Orderorder=(Order)message.getHeaders().get("order"); log.info("發貨,狀態機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.WAIT_RECEIVE.getKey()); orderMapper.updateById(order); //TODO其他業務 } @OnTransition(source="WAIT_RECEIVE",target="FINISH") publicvoidreceiveTransition(Message message){ Orderorder=(Order)message.getHeaders().get("order"); log.info("確認收貨,狀態機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.FINISH.getKey()); orderMapper.updateById(order); //TODO其他業務 } }
3.3 測試驗證
1)驗證業務
新增一個訂單
http://localhost:8084/order/create
對訂單進行支付
http://localhost:8084/order/pay?id=2
對訂單進行發貨
http://localhost:8084/order/deliver?id=2
對訂單進行確認收貨
http://localhost:8084/order/receive?id=2
正常流程結束。如果對一個訂單進行支付了,再次進行支付,則會報錯:http://localhost:8084/order/pay?id=2
報錯如下:
2)驗證持久化
內存
使用內存持久化類持久化:
/** *author:芋道源碼 */ @Resource privateStateMachinePersisterstateMachineMemPersister; /** *發送訂單狀態轉換事件 *synchronized修飾保證這個方法是線程安全的 * *@paramchangeEvent *@paramorder *@return */ privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態機 orderStateMachine.start(); //嘗試恢復狀態機狀態 stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); //持久化狀態機狀態 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; /** *發送訂單狀態轉換事件 *synchronized修飾保證這個方法是線程安全的 * *@paramchangeEvent *@paramorder *@return */ privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態機 orderStateMachine.start(); //嘗試恢復狀態機狀態 stateMachineRedisPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); //持久化狀態機狀態 stateMachineRedisPersister.persist(orderStateMachine,String.valueOf(order.getId())); }catch(Exceptione){ log.error("訂單操作失敗:{}",e); }finally{ orderStateMachine.stop(); } returnresult; }
3.4 狀態機存在的問題
1)stateMachine無法拋出異常,異常會被狀態機給消化掉
問題現象
從orderStateMachine.sendEvent(message);獲取的結果無法感知到。無論執行正常還是拋出異常,都返回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("支付,狀態機反饋信息:{}",message.getHeaders().toString()); try{ //更新訂單 order.setStatus(OrderStatus.WAIT_DELIVER.getKey()); orderMapper.updateById(order); //TODO其他業務 //模擬異常 if(Objects.equals(order.getName(),"A")){ thrownewRuntimeException("執行業務異常"); } }catch(Exceptione){ //如果出現異常,記錄異常信息,拋出異常信息進行回滾 log.error("payTransition出現異常:{}",e); throwe; } }
監聽事件拋出異常,在發送事件中無法感知:
privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態機 orderStateMachine.start(); //嘗試恢復狀態機狀態 stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); //事件執行異常了,依然返回true,無法感知異常 result=orderStateMachine.sendEvent(message); if(result){ //持久化狀態機狀態,如果根據true持久化,則會出現問題 stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId())); } }catch(Exceptione){ log.error("訂單操作失敗:{}",e); }finally{ orderStateMachine.stop(); } returnresult; }
調試發現:發送事件和監聽事件是一個線程,發送事件的結果是在監聽操作執行完之后才返回
監聽線程:
解決方案:自己保存異常到數據庫或者內存中,進行判斷
也可以通過接口:org.springframework.statemachine.StateMachine##getExtendedState
方法把執行狀態放入這個變量中
publicinterfaceExtendedState{ Map
org.springframework.statemachine.support.DefaultExtendedState##getVariables
privatefinalMap
改造監聽狀態:把業務的執行結果進行保存,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("支付,狀態機反饋信息:{}",message.getHeaders().toString()); try{ //更新訂單 order.setStatus(OrderStatus.WAIT_DELIVER.getKey()); orderMapper.updateById(order); //TODO其他業務 //模擬異常 if(Objects.equals(order.getName(),"A")){ thrownewRuntimeException("執行業務異常"); } //成功則為1 orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1); }catch(Exceptione){ //如果出現異常,則進行回滾 log.error("payTransition出現異常:{}",e); //將異常信息變量信息中,失敗則為0 orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),0); throwe; } }
發送事件改造:如果獲取到業務執行異常,則返回失敗,不進行狀態機持久化 com.zengqingfa.springboot.state.demo.service.impl.OrderServiceImpl##sendEvent
@Resource privateStateMachineorderStateMachine; @Resource privateStateMachinePersister stateMachineMemPersister; /** *發送訂單狀態轉換事件 *synchronized修飾保證這個方法是線程安全的 * *@paramchangeEvent *@paramorder *@return */ privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder){ booleanresult=false; try{ //啟動狀態機 orderStateMachine.start(); //嘗試恢復狀態機狀態 stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); if(!result){ returnfalse; } //獲取到監聽的結果信息 Integero=(Integer)orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition+order.getId()); //操作完成之后,刪除本次對應的key信息 orderStateMachine.getExtendedState().getVariables().remove(CommonConstants.payTransition+order.getId()); //如果事務執行成功,則持久化狀態機 if(Objects.equals(1,Integer.valueOf(o))){ //持久化狀態機狀態 stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId())); }else{ //訂單執行業務異常 returnfalse; } }catch(Exceptione){ log.error("訂單操作失敗:{}",e); }finally{ orderStateMachine.stop(); } returnresult; }
代碼優化
發送事件只針對了支付,如果是非支付事件呢?
//獲取到監聽的結果信息 Integero=(Integer)orderStateMachine.getExtendedState().getVariables().get(CommonConstants.payTransition+order.getId());
監聽設置狀態的代碼有重復代碼,需要進行優化,可使用aop
try{ //TODO其他業務 //成功則為1 orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1); }catch(Exceptione){ //如果出現異常,則進行回滾 log.error("payTransition出現異常:{}",e); //將異常信息變量信息中,失敗則為0 orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),0); throwe; }
常量類:
publicinterfaceCommonConstants{ StringorderHeader="order"; StringpayTransition="payTransition"; StringdeliverTransition="deliverTransition"; StringreceiveTransition="receiveTransition"; }
支付發送事件: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("線程名稱:{},支付失敗,狀態異常,訂單信息:{}",Thread.currentThread().getName(),order); thrownewRuntimeException("支付失敗,訂單狀態異常"); } returnorder; } /** *發送訂單狀態轉換事件 *synchronized修飾保證這個方法是線程安全的 * *@paramchangeEvent *@paramorder *@return */ privatesynchronizedbooleansendEvent(OrderStatusChangeEventchangeEvent,Orderorder,Stringkey){ booleanresult=false; try{ //啟動狀態機 orderStateMachine.start(); //嘗試恢復狀態機狀態 stateMachineMemPersister.restore(orderStateMachine,String.valueOf(order.getId())); Messagemessage=MessageBuilder.withPayload(changeEvent).setHeader("order",order).build(); result=orderStateMachine.sendEvent(message); if(!result){ returnfalse; } //獲取到監聽的結果信息 Integero=(Integer)orderStateMachine.getExtendedState().getVariables().get(key+order.getId()); //操作完成之后,刪除本次對應的key信息 orderStateMachine.getExtendedState().getVariables().remove(key+order.getId()); //如果事務執行成功,則持久化狀態機 if(Objects.equals(1,Integer.valueOf(o))){ //持久化狀態機狀態 stateMachineMemPersister.persist(orderStateMachine,String.valueOf(order.getId())); }else{ //訂單執行業務異常 returnfalse; } }catch(Exceptione){ log.error("訂單操作失敗:{}",e); }finally{ orderStateMachine.stop(); } returnresult; }
使用aop對監聽事件切面,把業務執行結果封裝到狀態機的變量中,注解:
@Retention(RetentionPolicy.RUNTIME) public@interfaceLogResult{ /** *執行的業務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{ //獲取參數 Object[]args=pjp.getArgs(); log.info("參數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{ //執行方法 returnVal=pjp.proceed(); //如果業務執行正常,則保存信息 //成功則為1 orderStateMachine.getExtendedState().getVariables().put(key+order.getId(),1); }catch(Throwablee){ log.error("e:{}",e.getMessage()); //如果業務執行異常,則保存信息 //將異常信息變量信息中,失敗則為0 orderStateMachine.getExtendedState().getVariables().put(key+order.getId(),0); throwe; } returnreturnVal; } }
監聽類使用注解:
@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("支付,狀態機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.WAIT_DELIVER.getKey()); orderMapper.updateById(order); //TODO其他業務 //模擬異常 if(Objects.equals(order.getName(),"A")){ thrownewRuntimeException("執行業務異常"); } } @OnTransition(source="WAIT_DELIVER",target="WAIT_RECEIVE") @LogResult(key=CommonConstants.deliverTransition) publicvoiddeliverTransition(Message message){ Orderorder=(Order)message.getHeaders().get("order"); log.info("發貨,狀態機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.WAIT_RECEIVE.getKey()); orderMapper.updateById(order); //TODO其他業務 } @OnTransition(source="WAIT_RECEIVE",target="FINISH") @LogResult(key=CommonConstants.receiveTransition) publicvoidreceiveTransition(Message message){ Orderorder=(Order)message.getHeaders().get("order"); log.info("確認收貨,狀態機反饋信息:{}",message.getHeaders().toString()); //更新訂單 order.setStatus(OrderStatus.FINISH.getKey()); orderMapper.updateById(order); //TODO其他業務 } }
-
編程
+關注
關注
88文章
3614瀏覽量
93686 -
spring
+關注
關注
0文章
340瀏覽量
14338 -
數學模型
+關注
關注
0文章
83瀏覽量
11933 -
自動機
+關注
關注
1文章
28瀏覽量
9261
原文標題:項目終于用上了Spring狀態機,非常優雅!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論