最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2026年01月10日 14:50:56   作者:不愿放下技術的小趙  
本文介紹了Spring State Machine框架的基本概念、核心特性、注解驅動開發(fā)方式以及如何在Spring Boot項目中集成狀態(tài)機,通過狀態(tài)機,可以有效地管理復雜的狀態(tài)流轉邏輯,文章還討論了狀態(tài)機的持久化問題,并提供了一個自定義持久化實現(xiàn)的示例,感興趣的朋友跟隨小編一起看看吧

一、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-coreSpring Statemachine的核心系統(tǒng)。
spring-statemachine-recipes-common不需要核心框架之外的依賴項的常見配方。
spring-statemachine-kryoKryoSpring Statemachine的序列化程序。
spring-statemachine-data-commonSpring 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-clusterSpring Cloud Cluster的支持模塊。請注意,Spring Cloud Cluster已被Spring Integration取代。
spring-statemachine-uml使用Eclipse Papyrus進行UI UML建模的支持模塊。
spring-statemachine-autoconfigureSpring 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)模式的缺點:

  1. 狀態(tài)模式的使用必然會增加系統(tǒng)的類與對象的個數(shù)。
  2. 狀態(tài)模式的結構與實現(xiàn)都較為復雜,如果使用不當會導致程序結構和代碼的混亂。
  3. 狀態(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:訂單-支付訂單接口

  • 日志打?。?/li>

測試2:訂單-收貨確認接口

  • 日志打?。?/li>

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

  • 日志打?。?/li>

測試4:查詢狀態(tài)接口

問題點:所有訂單共享一個狀態(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加密算法數(shù)字簽名實現(xiàn)原理詳解

    JAVA加密算法數(shù)字簽名實現(xiàn)原理詳解

    這篇文章主要介紹了JAVA加密算法數(shù)字簽名實現(xiàn)原理詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-10-10
  • Java枚舉的使用與反射應用方式

    Java枚舉的使用與反射應用方式

    枚舉類型是一種特殊的類,限定為固定實例集合,且是類型安全和線程安全的,枚舉類型不可繼承,但可以添加屬性和方法,支持單例模式,枚舉常量可以通過反射獲取和操作,提供了靈活性和擴展性
    2024-09-09
  • 詳解Java8新特性如何防止空指針異常

    詳解Java8新特性如何防止空指針異常

    要說 Java 編程中哪個異常是你印象最深刻的,那 NullPointerException 空指針可以說是臭名昭著的,不要說初級程序員會碰到, 即使是中級,專家級程序員稍不留神,就會掉入這個坑里,本文就和大家聊聊Java8新特性如何防止空指針異常
    2023-08-08
  • Spring Boot 快速搭建微服務框架詳細教程

    Spring Boot 快速搭建微服務框架詳細教程

    SpringBoot是為了簡化Spring應用的創(chuàng)建、運行、調試、部署等而出現(xiàn)的,使用它可以做到專注于Spring應用的開發(fā),而無需過多關注XML的配置。本文重點給大家介紹Spring Boot 快速搭建微服務框架詳細教程,需要的的朋友參考下吧
    2017-09-09
  • java 服務器接口快速開發(fā)之servlet詳細教程

    java 服務器接口快速開發(fā)之servlet詳細教程

    Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務程序或服務連接器,用Java編寫的服務器端程序,具有獨立于平臺和協(xié)議的特性,主要功能在于交互式地瀏覽和生成數(shù)據,生成動態(tài)Web內容
    2021-06-06
  • Java微信公眾平臺開發(fā)(15) 微信JSSDK的使用

    Java微信公眾平臺開發(fā)(15) 微信JSSDK的使用

    這篇文章主要為大家詳細介紹了Java微信公眾平臺開發(fā)第十五步,微信JSSDK的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • Resty開發(fā)restful版本的Jfinal深入研究

    Resty開發(fā)restful版本的Jfinal深入研究

    這篇文章主要為大家介紹了Resty開發(fā)restful版本的Jfinal深入研究有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • SpringBoot中的maven插件spring-boot-maven-plugin使用

    SpringBoot中的maven插件spring-boot-maven-plugin使用

    這篇文章主要介紹了SpringBoot中的maven插件spring-boot-maven-plugin使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Springboot打包部署項目的過程詳解

    Springboot打包部署項目的過程詳解

    這篇文章主要介紹了Springboot打包部署項目的過程,通過jar包方式打包和war方式打包,本文結合示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-12-12
  • Spring使用Jackson實現(xiàn)轉換XML與Java對象

    Spring使用Jackson實現(xiàn)轉換XML與Java對象

    這篇文章主要為大家詳細介紹了Spring如何使用Jackson實現(xiàn)轉換XML與Java對象,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-02-02

最新評論

响水县| 景泰县| 崇礼县| 汶川县| 灌南县| 万州区| 昌宁县| 邹城市| 邳州市| 南漳县| 蓬溪县| 松原市| 交口县| 武强县| 三穗县| 池州市| 抚宁县| 辽宁省| 施甸县| 桦川县| 革吉县| 白山市| 云南省| 财经| 新乐市| 留坝县| 连云港市| 遵义县| 牙克石市| 武邑县| 云霄县| 洛川县| 沧州市| 曲周县| 固始县| 屏边| 博罗县| 永清县| 林西县| 图片| 台东县|