SpringBoot 集成 Statemachine的實戰(zhàn)指南

- 官網地址:https://spring.io/projects/spring-statemachine#learn
- 官方文檔:https://docs.spring.io/spring-statemachine/docs/3.2.1/reference/#statemachine-getting-started
一、Statemachine 簡介
Spring Statemachine 是一個由 Spring 團隊提供的 輕量級狀態(tài)機框架,它允許開發(fā)者以簡便且強大的方式管理復雜的狀態(tài)流轉邏輯。該框架建立在 有限狀態(tài)機(FSM) 的概念之上,提供了一種簡潔且靈活的方式來定義、管理和執(zhí)行狀態(tài)機。
1.1 核心概念
- 狀態(tài)(State):系統(tǒng)可能處于的不同條件或模式,是狀態(tài)機的核心組成單元。
- 事件(Event):觸發(fā)狀態(tài)轉換的動作或消息,是引起狀態(tài)機從當前狀態(tài)遷移到新狀態(tài)的原因。
- 轉換(Transition):描述了在何種條件下,當接收到特定事件時,系統(tǒng)可以從一個狀態(tài)轉移到另一個狀態(tài)。
- 動作(Action):在狀態(tài)轉換時執(zhí)行的具體操作。
1.2 主要特性
Spring Statemachine 提供了豐富的功能特性:
- 易于使用的平面(一級)狀態(tài)機,適用于簡單的用例;
- 分層狀態(tài)結構,以簡化復雜的狀態(tài)配置;
- 狀態(tài)機區(qū)域提供更復雜的狀態(tài)配置;
- 觸發(fā)器、轉換、守衛(wèi)和動作的使用;
- 類型安全的配置適配器;
- 狀態(tài)機事件監(jiān)聽器;
- Spring IoC 集成將 Bean 與 狀態(tài)機 相關聯(lián)。
1.3 注解驅動開發(fā)方式
為了簡化開發(fā),可以使用 Statemachine 的 注解驅動開發(fā)方式,特別是 @OnTransition、@OnTransitionStart、@OnTransitionEnd 和 @WithStateMachine 注解的使用,這些注解能夠讓我們以更加 聲明式 和 簡介 的方式處理狀態(tài)轉換邏輯。
注解驅動的開發(fā)方式具有以下優(yōu)勢:
- 代碼簡潔性:將狀態(tài)轉換邏輯直接注解在方法上,減少模板代碼;
- 關注點分離:業(yè)務邏輯與狀態(tài)機配置清晰分離,提高可維護性;
- 類型安全:編譯時檢查注解的正確性,減少運行時錯誤;
- 可讀性強:通過注解直觀地表達狀態(tài)轉換的意圖。
1.4 核心注解詳解
1)@WithStateMachine 注解
@WithStateMachine 注解用于標識一個類是與狀態(tài)機相關的監(jiān)聽器,它告訴 Spring 這個類中的方法需要接受狀態(tài)機的事件通知。
- 使用場景:標記狀態(tài)機監(jiān)聽器類,是類中的狀態(tài)轉換注解生效。
2)@OnTransition 注解
@OnTrasition 注解用于標記在狀態(tài)轉換發(fā)生時執(zhí)行的方法,它不區(qū)分轉換的開始和結束。
- 使用場景:當不關心轉換的具體階段,只需要在轉換發(fā)生時執(zhí)行某些邏輯時使用。
3)@OnTransitionStart 注解
@OnTransitionStart 注解用于標記在狀態(tài)轉換開始時執(zhí)行的方法。
- 使用場景:需要在狀態(tài)轉換剛開始時執(zhí)行與處理邏輯,如參數(shù)驗證、資源準備等。
4)@OnTransitionEnd 注解
@OnTransitionEnd 注解用于標記在狀態(tài)轉換 結束時 執(zhí)行的方法。
- 使用場景:需要在狀態(tài)轉換完成后執(zhí)行清理邏輯、記錄日志、發(fā)送通知等。
1.5 包含的模塊
Spring Statemachine 包含的模塊如下:
| 模塊 | 描述 |
|---|---|
spring-statemachine-core | Spring Statemachine的核心系統(tǒng)。 |
spring-statemachine-recipes-common | 不需要核心框架之外的依賴項的常見配方。 |
spring-statemachine-kryo | KryoSpring Statemachine的序列化程序。 |
spring-statemachine-data-common | Spring Data的通用支持模塊。 |
spring-statemachine-data-jpa | 支持Spring Data JPA模塊。 |
spring-statemachine-data-redis | 支持Spring Data Redis模塊。 |
spring-statemachine-data-mongodb | 支持Spring Data MongoDB模塊。 |
spring-statemachine-zookeeper | 分布式狀態(tài)機的Zooeman集成。 |
spring-statemachine-test | 狀態(tài)機測試支持模塊。 |
spring-statemachine-cluster | Spring Cloud Cluster的支持模塊。請注意,Spring Cloud Cluster已被Spring Integration取代。 |
spring-statemachine-uml | 使用Eclipse Papyrus進行UI UML建模的支持模塊。 |
spring-statemachine-autoconfigure | Spring Boot的支持模塊。 |
spring-statemachine-bom | 物料清單pom。 |
spring-statemachine-starter | 彈簧啟動啟動器。 |
二、知識回顧——狀態(tài)模式
2.1 什么是狀態(tài)模式?
狀態(tài)模式(State Pattern) 是一種 行為型 設計模式,對有狀態(tài)的對象,把復雜的 “判斷邏輯” 提取到不同的狀態(tài)對象中,允許狀態(tài)對象在其內部狀態(tài)發(fā)生改變時,改變其行為。
2.2 狀態(tài)模式的優(yōu)缺點
狀態(tài)模式的優(yōu)點:
- 結構清晰 :狀態(tài)模式將與特定狀態(tài)相關的行為局部化道一個狀態(tài)中,并且將不同狀態(tài)的行為分割開來,滿足 “但一職責原則”。
- 將狀態(tài)轉換顯示化:減少對象間的相互依賴,將不同的狀態(tài)引入獨立的對象中會是的狀態(tài)轉換變得更加明確,且減少對相見的相互依賴。
- 狀態(tài)類職責明確:有利于程序的擴展。通過定義新的子類很容易地增加新的狀態(tài)和轉換。
狀態(tài)模式的缺點:
- 狀態(tài)模式的使用必然會增加系統(tǒng)的類與對象的個數(shù)。
- 狀態(tài)模式的結構與實現(xiàn)都較為復雜,如果使用不當會導致程序結構和代碼的混亂。
- 狀態(tài)模式對開閉原則的支持并不太好,對于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負責狀態(tài)轉換的源碼,否則無法切換到新增狀態(tài),而且修改某個狀態(tài)類的行為也需要修改對應類的源碼。
2.3 狀態(tài)模式的實現(xiàn)結構
狀態(tài)模式把受環(huán)境改變的對象行為包裝在不同的狀態(tài)對象里,其意圖是讓一個對象在其內部狀態(tài)改變的時候,其行為也隨之改變?,F(xiàn)在我們來分析其基本結構和實現(xiàn)方法。
狀態(tài)模式主要包含三個角色:
- Context(環(huán)境類):定義客戶端感興趣的接口,維護一個 State 子類的實例,這個示例定義當前狀態(tài)。
- State(抽象狀態(tài)類):定義一個接口,用以封裝 Context 的特定狀態(tài)相關的行為。
- ConcreteState(具體狀態(tài)類):每一個子類實現(xiàn)一個與 Context 的一個狀態(tài)相關的行為。
三、SpringBoot 集成
項目結構如下:

3.1 Maven 依賴
對于 SpringBoot 2.x 項目,可以使用 2.x 版本的 StateMachine 依賴:
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-kryo</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>3.2 定義狀態(tài)和事件枚舉
首先,我們需要定義狀態(tài)機和事件的所有可能值。以訂單系統(tǒng)為例:
OrderStatesEnum.java、OrderEventsEnum.java
public enum OrderStatesEnum {
UNPAID, // 待支付
WAITING_FOR_RECEIVE, // 待收貨
DONE, // 完成
CANCELLED // 取消
}
public enum OrderEventsEnum {
PAY, // 支付
RECEIVE, // 收貨
CANCEL // 取消
}3.3 配置狀態(tài)機
接下來,我們需要配置狀態(tài)機,定義狀態(tài)轉換規(guī)則:
StateMachineConfig.java
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import java.util.EnumSet;
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class StateMachineConfig extends StateMachineConfigurerAdapter<OrderStatesEnum, OrderEventsEnum> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatesEnum, OrderEventsEnum> states) throws Exception {
states
.withStates()
.initial(OrderStatesEnum.UNPAID)
.states(EnumSet.allOf(OrderStatesEnum.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatesEnum, OrderEventsEnum> transitions) throws Exception {
transitions
.withExternal()
.source(OrderStatesEnum.UNPAID).target(OrderStatesEnum.WAITING_FOR_RECEIVE)
.event(OrderEventsEnum.PAY)
.and()
.withExternal()
.source(OrderStatesEnum.WAITING_FOR_RECEIVE).target(OrderStatesEnum.DONE)
.event(OrderEventsEnum.RECEIVE)
.and()
.withExternal()
.source(OrderStatesEnum.UNPAID).target(OrderStatesEnum.CANCELLED)
.event(OrderEventsEnum.CANCEL);
}
}3.4 使用注解實現(xiàn)狀態(tài)監(jiān)聽器
這段代碼展示如何使用注解來監(jiān)聽狀態(tài)轉換:
OrderStateListener.java
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.OnTransitionEnd;
import org.springframework.statemachine.annotation.OnTransitionStart;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListener {
/**
* 支付轉換開始時的處理
*/
@OnTransitionStart(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void onPayStart(StateContext<OrderStatesEnum, OrderEventsEnum> context) {
log.info("【支付轉換開始】開始處理支付邏輯");
// 獲取轉換相關的數(shù)據
Object paymentData = context.getMessageHeader("paymentData");
if (paymentData != null) {
log.info("支付數(shù)據:{}", paymentData);
}
// 執(zhí)行支付前的驗證邏輯
log.info("驗證支付參數(shù)...");
log.info("檢查庫存...");
log.info("預扣庫存...");
}
/**
* 支付轉換結束時的處理
*/
@OnTransitionEnd(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void onPayEnd(StateContext<OrderStatesEnum, OrderEventsEnum> context) {
log.info("【支付轉換結束】支付處理完成");
// 執(zhí)行支付后的清理邏輯
log.info("更新庫存...");
log.info("生成支付憑證...");
log.info("發(fā)送支付成功通知...");
// 記錄轉換耗時
Long startTime = (Long) context.getMessageHeader("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
log.info("支付處理耗時:{}ms", duration);
}
}
/**
* 收貨轉換開始時的處理
*/
@OnTransitionStart(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void onReceiveStart() {
log.info("【收貨轉換開始】開始確認收貨");
log.info("驗證收貨權限...");
log.info("檢查物流信息...");
}
/**
* 收貨轉換結束時的處理
*/
@OnTransitionEnd(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void onReceiveEnd() {
log.info("【收貨轉換結束】收貨確認完成");
log.info("更新訂單完成時間...");
log.info("計算商家評分...");
log.info("發(fā)送訂單完成通知...");
}
/**
* 取消訂單轉換開始時的處理
*/
@OnTransitionStart(source = "UNPAID", target = "CANCELLED")
public void onCancelStart(StateContext<OrderStatesEnum, OrderEventsEnum> context) {
log.info("【取消轉換開始】開始取消訂單");
String cancelReason = context.getMessageHeaders().get("cancelReason", String.class);
log.info("取消原因: {}", cancelReason);
log.info("驗證取消權限...");
}
/**
* 取消訂單轉換結束時的處理
*/
@OnTransitionEnd(source = "UNPAID", target = "CANCELLED")
public void onCancelEnd() {
log.info("【取消轉換結束】訂單取消完成");
log.info("釋放庫存...");
log.info("發(fā)送取消通知...");
log.info("記錄取消日志...");
}
/**
* 通用的狀態(tài)轉換處理(不區(qū)分開始和結束)
*/
@OnTransition
public void onAnyTransition() {
log.info("【通用轉換】狀態(tài)發(fā)生變化");
}
/**
* 從任意狀態(tài)到指定狀態(tài)的轉換結束處理
*/
@OnTransitionEnd(target = "DONE")
public void onTransitionToDone() {
log.info("【到達完成狀態(tài)】訂單流程結束");
log.info("執(zhí)行訂單完成后的統(tǒng)計任務...");
log.info("更新用戶積分...");
}
}3.5 業(yè)務服務類
在業(yè)務服務類中使用狀態(tài)機:
OrderService.java
import com.demo.enums.OrderStatesEnum;
import java.util.Map;
public interface OrderService {
/**
* 處理支付
*/
void payOrder(String orderId, Map<String, Object> paymentData);
/**
* 確認收貨
*/
void confirmReceive(String orderId);
/**
* 取消訂單
*/
void cancelOrder(String orderId, String reason);
/**
* 獲取當前狀態(tài)
*/
OrderStatesEnum getCurrentState();
}OrderServiceImpl.java
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import com.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private StateMachine<OrderStatesEnum, OrderEventsEnum> stateMachine;
@Override
public void payOrder(String orderId, Map<String, Object> paymentData) {
log.info("開始處理訂單任務狀態(tài)事件,orderId: {},paymentData:{}", orderId, paymentData);
// 設置消息頭,傳遞業(yè)務數(shù)據
Map<String, Object> headers = new HashMap<>();
headers.put("orderId", orderId);
headers.put("paymentData", paymentData);
headers.put("startTime", System.currentTimeMillis());
sendStateMachineEvent(OrderStatesEnum.UNPAID, OrderEventsEnum.PAY, headers);
}
@Override
public void confirmReceive(String orderId) {
log.info("開始處理確認收貨狀態(tài)事件,orderId: {}", orderId);
Map<String, Object> headers = new HashMap<>();
headers.put("orderId", orderId);
sendStateMachineEvent(OrderStatesEnum.WAITING_FOR_RECEIVE, OrderEventsEnum.RECEIVE, headers);
}
@Override
public void cancelOrder(String orderId, String reason) {
log.info("開始處理取消訂單狀態(tài)事件,orderId: {},reason:{}", orderId, reason);
Map<String, Object> headers = new HashMap<>();
headers.put("orderId", orderId);
headers.put("cancelReason", reason);
sendStateMachineEvent(OrderStatesEnum.UNPAID, OrderEventsEnum.CANCEL, headers);
}
/**
* 發(fā)送狀態(tài)機事件的通用方法
* @param currentState 當前狀態(tài)
* @param event 要發(fā)送的事件
* @param headers 消息頭數(shù)據
*/
private void sendStateMachineEvent(OrderStatesEnum currentState, OrderEventsEnum event, Map<String, Object> headers) {
// 啟動狀態(tài)機
stateMachine.start();
// 根據當前任務狀態(tài)設置狀態(tài)機狀態(tài)
log.info("當前任務狀態(tài): {}", currentState);
// 將狀態(tài)機的狀態(tài)設置為業(yè)務對象的實際狀態(tài)
stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> {
accessor.resetStateMachine(new org.springframework.statemachine.support.DefaultStateMachineContext<>(
currentState, null, null, null));
});
// 構建并發(fā)送消息
Message<OrderEventsEnum> message = MessageBuilder
.withPayload(event)
.copyHeaders(headers)
.build();
stateMachine.sendEvent(message);
}
@Override
public OrderStatesEnum getCurrentState() {
State<OrderStatesEnum, OrderEventsEnum> state = stateMachine.getState();
return state == null ? null : state.getId();
}
}3.6 控制器類
提供 REST API 接口:
OrderController.java
import com.demo.common.Result;
import com.demo.enums.OrderStatesEnum;
import com.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* <p> @Title DemoController
* <p> @Description 測試Controller
*
* @author ACGkaka
* @date 2023/4/24 18:02
*/
@Slf4j
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/{orderId}/pay")
public Result<Object> payOrder(@PathVariable String orderId,
@RequestBody Map<String, Object> paymentData) {
try {
orderService.payOrder(orderId, paymentData);
return Result.succeed("支付處理中");
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.failed("支付失?。? + e.getMessage());
}
}
@PostMapping("/{orderId}/receive")
public Result<Object> receiveOrder(@PathVariable String orderId) {
try {
orderService.confirmReceive(orderId);
return Result.succeed("收貨確認處理中");
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.failed("收貨確認失敗:" + e.getMessage());
}
}
@PostMapping("/{orderId}/cancel")
public Result<Object> cancelOrder(@PathVariable String orderId,
@RequestParam String reason) {
try {
orderService.cancelOrder(orderId, reason);
return Result.succeed("取消訂單處理中");
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.failed("取消訂單失?。? + e.getMessage());
}
}
@GetMapping("/{orderId}/status")
public Result<OrderStatesEnum> getOrderStatus() {
try {
Result<OrderStatesEnum> result = new Result<>();
OrderStatesEnum currentState = orderService.getCurrentState();
return result.setData(currentState);
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.failed("獲取訂單狀態(tài)失敗:" + e.getMessage());
}
}
}3.7 測試結果
測試1:訂單-支付訂單接口
- 請求地址:http://localhost:8080/orders/1/pay
- 請求截圖:

- 日志打?。?/li>

測試2:訂單-收貨確認接口
- 請求地址:http://localhost:8080/orders/1/receive
- 請求截圖:

- 日志打?。?/li>

測試3:訂單-取消訂單接口

- 日志打?。?/li>

測試4:查詢狀態(tài)接口
- 請求地址:http://localhost:8080/orders/1/status
- 請求截圖:

問題點:所有訂單共享一個狀態(tài)
雖然已經完成了狀態(tài)機的基礎操作,但是這里會發(fā)現(xiàn)一個問題:整個狀態(tài)機只有一個狀態(tài)。也就是說不管是哪個訂單的狀態(tài)都是一樣的,那么有一個訂單的狀態(tài)為 DONE 的話,其余所有訂單都走不了流程了,只能重啟程序才能還原。
這肯定不行,所以就需要 將狀態(tài)進行持久化,根據訂單編號分別保存各自的狀態(tài)。
四、升級:狀態(tài)機持久化
使用 spring-statemachine 狀態(tài)機持久化時,可以通過內存、spring-statemachine-redis 或 spring-statemachine-data-jpa 現(xiàn)有方式進行持久化處理。
因項目狀態(tài)變化操作記錄頻繁,數(shù)據量大,使用 內存 或 spring-statemachine-redis 模式不可取,而項目使用的是 MyBatis,使用 spring-statemachine-data-jpa 也不合適,需要自定義實現(xiàn)。
項目結構如下:

4.1 Maven 依賴
<!-- Statemachine -->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-kryo</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>4.2 自定義持久化類
CustomStateMachinePersist.java
import com.demo.common.redis.util.RedisUtil;
import com.demo.domain.OrderInfo;
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.kryo.MessageHeadersSerializer;
import org.springframework.statemachine.kryo.StateMachineContextSerializer;
import org.springframework.statemachine.kryo.UUIDSerializer;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.UUID;
/**
* 自定義狀態(tài)機持久化
*/
@Slf4j
@Configuration
public class CustomStateMachinePersist {
private static final String REDIS_KEY_PREFIX = "ORDER_STATE_V1_";
@Autowired
private RedisUtil redisUtil;
private static final ThreadLocal<Kryo> KRYO_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer<>());
kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer());
kryo.addDefaultSerializer(UUID.class, new UUIDSerializer());
// 設置引用追蹤策略
kryo.setReferences(true);
return kryo;
});
private <S, E> byte[] serialize(StateMachineContext<S, E> context) {
Kryo kryo = KRYO_THREAD_LOCAL.get();
// 重置引用狀態(tài),避免狀態(tài)污染
kryo.reset();
ByteArrayOutputStream out = new ByteArrayOutputStream();
Output output = new Output(out);
try {
kryo.writeObject(output, context);
output.flush();
return out.toByteArray();
} catch (Exception e) {
log.error("序列化狀態(tài)機上下文失敗", e);
throw new RuntimeException("序列化失敗", e);
} finally {
output.close();
}
}
private <S, E> StateMachineContext<S, E> deserialize(byte[] data) {
if (data == null || data.length == 0) {
log.info("反序列化數(shù)據為空");
return null;
}
Kryo kryo = KRYO_THREAD_LOCAL.get();
// 重置引用狀態(tài),避免狀態(tài)污染
kryo.reset();
ByteArrayInputStream in = new ByteArrayInputStream(data);
Input input = new Input(in);
try {
return kryo.readObject(input, StateMachineContext.class);
} catch (IndexOutOfBoundsException e) {
log.error("反序列化失敗,可能是數(shù)據損壞或版本不兼容. 數(shù)據長度: {}, 錯誤: {}",
data.length, e.getMessage());
throw new RuntimeException("反序列化失敗", e);
} catch (Exception e) {
log.error("反序列化狀態(tài)機上下文時發(fā)生未知錯誤", e);
throw new RuntimeException("反序列化失敗", e);
} finally {
input.close();
}
}
/**
* 狀態(tài)機持久化
*/
@Bean
public DefaultStateMachinePersister<OrderStatesEnum, OrderEventsEnum, OrderInfo> stateMachinePersister(){
return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatesEnum, OrderEventsEnum, OrderInfo>() {
@Override
public void write(StateMachineContext<OrderStatesEnum, OrderEventsEnum> context, OrderInfo info) throws Exception {
String key = REDIS_KEY_PREFIX + info.getId();
try {
byte[] value = serialize(context);
log.info("正在寫入任務 {} 的狀態(tài)機上下文,狀態(tài)為 {}", info.getId(), context.getState());
redisUtil.set(key, value);
log.info("任務 {} 的狀態(tài)機上下文已成功寫入Redis", info.getId());
} catch (Exception e) {
log.error("寫入任務 {} 的狀態(tài)機上下文失敗", info.getId(), e);
throw e;
}
}
@Override
public StateMachineContext<OrderStatesEnum, OrderEventsEnum> read(OrderInfo info) throws Exception {
String key = REDIS_KEY_PREFIX + info.getId();
log.info("正在讀取任務 {} 的狀態(tài)機上下文", info.getId());
try {
byte[] value = (byte[]) redisUtil.get(key);
if (value == null) {
log.info("未找到任務 {} 的狀態(tài)機上下文", info.getId());
return null;
}
StateMachineContext<OrderStatesEnum, OrderEventsEnum> context = deserialize(value);
if (context == null) {
log.error("反序列化任務 {} 的狀態(tài)機上下文失敗,可能需要重新初始化", info.getId());
// 清理損壞的數(shù)據
redisUtil.delete(key);
return null;
}
log.info("已從Redis讀取任務 {} 的狀態(tài)機上下文,狀態(tài)為 {}", info.getId(), context.getState());
return context;
} catch (Exception e) {
log.error("讀取任務 {} 的狀態(tài)機上下文時發(fā)生錯誤", info.getId(), e);
throw e;
}
}
});
}
}4.3 編寫狀態(tài)機工具類
CustomStateMachineUtil.java
import com.demo.enums.OrderStatesEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class CustomStateMachineUtil<S, E, T> {
@Resource
private StateMachine<S, E> orderStateMachine;
@Resource
private StateMachinePersister<S, E, T> orderStateMachinePersister;
/**
* 發(fā)送狀態(tài)機事件的通用方法
* @param currentState 當前狀態(tài)
* @param event 要發(fā)送的事件
* @param info 消息實體
*/
public synchronized void sendEvent(S currentState, E event, T info) {
log.info("開始處理狀態(tài)機事件: {}", info);
try {
// 啟動狀態(tài)機
orderStateMachine.start();
// 設置消息頭,傳遞業(yè)務數(shù)據
Map<String, Object> headers = new HashMap<>();
headers.put("info", info);
headers.put("startTime", System.currentTimeMillis());
// 根據當前狀態(tài)設置狀態(tài)機狀態(tài)
log.info("當前狀態(tài): {}", currentState);
// 方式一:從Redis恢復狀態(tài)機狀態(tài)
orderStateMachinePersister.restore(orderStateMachine, info);
// 方式二:將狀態(tài)機的狀態(tài)設置為業(yè)務對象的實際狀態(tài)
// stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> {
// accessor.resetStateMachine(new org.springframework.statemachine.support.DefaultStateMachineContext<>(
// currentState, null, null, null));
// });
// 構建并發(fā)送消息
Message<E> message = MessageBuilder
.withPayload(event)
.copyHeaders(headers)
.build();
orderStateMachine.sendEvent(message);
// 持久化狀態(tài)機狀態(tài)
boolean persistSuccess = persist(info);
if (!persistSuccess) {
throw new RuntimeException("狀態(tài)機持久化狀態(tài)失敗");
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
log.error("狀態(tài)機發(fā)送事件失敗. 事件: {}, 異常: {}", info, e.getMessage(), e);
throw new RuntimeException("狀態(tài)機發(fā)送事件失敗");
} finally {
if (Objects.nonNull(info)) {
log.info("當前狀態(tài): {}", currentState);
if (Arrays.asList(OrderStatesEnum.DONE, OrderStatesEnum.CANCELLED).contains(currentState)) {
log.info("已完成或已取消,停止狀態(tài)機");
orderStateMachine.stop();
}
}
}
}
/**
* 持久化狀態(tài)機狀態(tài)
* @param info 實體
* @return 是否持久化成功
*/
public synchronized boolean persist(T info) {
try {
log.info("持久化狀態(tài)機開始,此時狀態(tài): {}", orderStateMachine.getState().getId());
// 啟用持久化:將狀態(tài)機狀態(tài)持久化到Redis
log.info("持久化已啟用,將狀態(tài)機狀態(tài)持久化到Redis");
orderStateMachinePersister.persist(orderStateMachine, info);
// 不啟用持久化:跳過持久化步驟
// log.info("持久化未啟用,跳過持久化步驟");
return true;
} catch (Exception e) {
log.error("持久化狀態(tài)機狀態(tài)失敗. 異常: {}", e.getMessage(), e);
return false;
}
}
}4.4 修改業(yè)務調用
OrderServiceImpl.java
import com.demo.common.stashmachine.util.CustomStateMachineUtil;
import com.demo.domain.OrderInfo;
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import com.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.statemachine.state.State;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private StateMachine<OrderStatesEnum, OrderEventsEnum> orderStateMachine;
@Autowired
private StateMachinePersister<OrderStatesEnum, OrderEventsEnum, OrderInfo> orderStateMachinePersister;
@Autowired
private CustomStateMachineUtil<OrderStatesEnum, OrderEventsEnum, OrderInfo> customStateMachineUtil;
@Override
public void payOrder(String orderId) {
log.info("開始處理訂單任務狀態(tài)事件,orderId: {}", orderId);
// 獲取訂單信息
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(orderId);
orderInfo.setState(OrderStatesEnum.UNPAID);
customStateMachineUtil.sendEvent(orderInfo.getState(), OrderEventsEnum.PAY, orderInfo);
}
@Override
public void confirmReceive(String orderId) {
log.info("開始處理確認收貨狀態(tài)事件,orderId: {}", orderId);
// 獲取訂單信息
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(orderId);
orderInfo.setState(OrderStatesEnum.WAITING_FOR_RECEIVE);
customStateMachineUtil.sendEvent(orderInfo.getState(), OrderEventsEnum.RECEIVE, orderInfo);
}
@Override
public void cancelOrder(String orderId, String reason) {
log.info("開始處理取消訂單狀態(tài)事件,orderId: {},reason:{}", orderId, reason);
// 獲取訂單信息
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(orderId);
orderInfo.setState(OrderStatesEnum.UNPAID);
orderInfo.setReason(reason);
customStateMachineUtil.sendEvent(orderInfo.getState(), OrderEventsEnum.CANCEL, orderInfo);
}
@Override
public OrderStatesEnum getCurrentState(String orderId) throws Exception {
OrderInfo info = new OrderInfo();
info.setId(orderId);
// 從Redis恢復狀態(tài)機狀態(tài)
orderStateMachinePersister.restore(orderStateMachine, info);
State<OrderStatesEnum, OrderEventsEnum> state = orderStateMachine.getState();
return state == null ? null : state.getId();
}
}4.5 測試結果
這次升級之后,再次調用接口可以發(fā)現(xiàn),不同的訂單編號已經可以分別存儲不同的狀態(tài)了,Redis 緩存內容如下:

整理完畢,完結撒花~??
參考地址:
1.SpringBoot集成spring-statemachine狀態(tài)機實現(xiàn)業(yè)務流程,https://blog.csdn.net/weixin_37598243/article/details/140907763
2.spring-statemachine 狀態(tài)機自定義持久化入庫,https://blog.csdn.net/sjy_2010/article/details/133862831
3.SpringBoot集成Spring Statemachine(狀態(tài)機)完整示例,https://juejin.cn/post/7441760738458779684
到此這篇關于SpringBoot 集成 Statemachine的實戰(zhàn)指南的文章就介紹到這了,更多相關SpringBoot 集成 Statemachine內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java 服務器接口快速開發(fā)之servlet詳細教程
Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務程序或服務連接器,用Java編寫的服務器端程序,具有獨立于平臺和協(xié)議的特性,主要功能在于交互式地瀏覽和生成數(shù)據,生成動態(tài)Web內容2021-06-06
Java微信公眾平臺開發(fā)(15) 微信JSSDK的使用
這篇文章主要為大家詳細介紹了Java微信公眾平臺開發(fā)第十五步,微信JSSDK的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
Resty開發(fā)restful版本的Jfinal深入研究
這篇文章主要為大家介紹了Resty開發(fā)restful版本的Jfinal深入研究有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03
SpringBoot中的maven插件spring-boot-maven-plugin使用
這篇文章主要介紹了SpringBoot中的maven插件spring-boot-maven-plugin使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
Spring使用Jackson實現(xiàn)轉換XML與Java對象
這篇文章主要為大家詳細介紹了Spring如何使用Jackson實現(xiàn)轉換XML與Java對象,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-02-02

