成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

哭了!為什么沒早用 Spring 狀態(tài)機?現(xiàn)在代碼優(yōu)雅到發(fā)光

開發(fā) 前端
Spring 狀態(tài)機還有很多高級特性和應用場景等待我們?nèi)ヌ剿鳎热绻ぷ髁饕妗⒂邢逘顟B(tài)自動機等。只要我們合理運用,它就能成為我們開發(fā)過程中的得力助手,讓我們的代碼質(zhì)量更上一層樓。

兄弟們,有沒有那么一瞬間,看著自己寫的那些處理狀態(tài)邏輯的代碼,恨不得給自己來兩拳?明明需求看起來挺簡單,就是處理個狀態(tài)轉換,結果寫著寫著,代碼里全是各種 if-else 或者 switch,層層嵌套,跟迷宮似的。不僅自己看著頭疼,同事接手的時候,估計心里也在默默問候咱的祖宗十八代。而且最要命的是,稍微不注意,狀態(tài)判斷錯了,bug 就跟雨后春筍似的冒出來,debug 都能讓人 debug 到懷疑人生。

咱就拿一個常見的訂單業(yè)務來說吧。訂單有創(chuàng)建、支付、發(fā)貨、收貨、取消、退款等等狀態(tài)。一開始,咱可能想著,這不簡單嘛,用 if-else 來判斷當前狀態(tài),然后根據(jù)不同的事件,比如用戶支付、商家發(fā)貨等,來更新訂單狀態(tài)。于是代碼里就出現(xiàn)了這樣的場景:

if (order.getStatus() == OrderStatus.CREATED) {
    if (event == Event.PAY) {
        // 處理支付邏輯
        order.setStatus(OrderStatus.PAID);
    } else if (event == Event.CANCEL) {
        // 處理取消邏輯
        order.setStatus(OrderStatus.CANCELED);
    }
} else if (order.getStatus() == OrderStatus.PAID) {
    if (event == Event.SHIP) {
        // 處理發(fā)貨邏輯
        order.setStatus(OrderStatus.SHIPPED);
    } else if (event == Event.REFUND) {
        // 處理退款邏輯
        order.setStatus(OrderStatus.REFUNDED);
    }
}
// 后面還有一堆類似的判斷...

隨著業(yè)務的不斷擴展,狀態(tài)越來越多,事件也越來越復雜,這樣的代碼簡直就是一場災難。維護起來難不說,要是新增一個狀態(tài)或者修改一個狀態(tài)轉換規(guī)則,那得把整個代碼翻個底朝天,還生怕漏掉某個地方,導致出現(xiàn)奇怪的 bug。這時候,咱心里是不是在想,有沒有一種更優(yōu)雅的方式來處理狀態(tài)邏輯呢?別急,今天咱就來聊聊 Spring 狀態(tài)機,用了它,保準讓你的代碼優(yōu)雅到發(fā)光,再也不用為狀態(tài)邏輯處理而發(fā)愁。

一、啥是狀態(tài)機?先把概念搞明白

在說 Spring 狀態(tài)機之前,咱得先弄清楚啥是狀態(tài)機。其實狀態(tài)機這玩意兒,在咱們?nèi)粘I钪须S處可見。比如說自動售貨機,它有不同的狀態(tài),比如等待投幣、等待選擇商品、出貨、找零等。當我們投入硬幣(這就是一個事件),自動售貨機就會從等待投幣狀態(tài)轉換到等待選擇商品狀態(tài);當我們選擇了一個商品(又是一個事件),它就會根據(jù)商品價格和我們投入的硬幣金額進行判斷,如果金額足夠,就會轉換到出貨狀態(tài),同時可能還會找零。

再比如說電梯,它有停止、運行、開門、關門等狀態(tài)。當我們在某一層按了電梯按鈕(事件),電梯如果在運行狀態(tài),可能會繼續(xù)運行到目標樓層,然后停止并開門;如果電梯在停止狀態(tài),就會開門讓我們進去,然后關門運行到我們選擇的樓層。

從計算機科學的角度來說,狀態(tài)機(State Machine)是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉移和動作等行為的數(shù)學模型。簡單來說,它由狀態(tài)(State)、事件(Event)、轉換(Transition)、動作(Action)和守衛(wèi)條件(Guard)組成。

  • 狀態(tài)(State):對象在其生命周期中的一種條件,比如訂單的創(chuàng)建狀態(tài)、支付狀態(tài)等。
  • 事件(Event):觸發(fā)狀態(tài)轉換的消息,比如用戶支付訂單、商家發(fā)貨等。
  • 轉換(Transition):從一個狀態(tài)到另一個狀態(tài)的遷移,通常由事件觸發(fā),并且可能需要滿足一定的守衛(wèi)條件。
  • 動作(Action):在狀態(tài)轉換過程中執(zhí)行的操作,比如更新訂單狀態(tài)、發(fā)送通知等。
  • 守衛(wèi)條件(Guard):一個布爾表達式,用于判斷事件是否能夠觸發(fā)狀態(tài)轉換,比如只有當訂單金額大于 0 時,才能進行支付操作。

狀態(tài)機的好處可太多了。它能讓我們清晰地描述對象的狀態(tài)變化過程,代碼結構更加清晰,易于維護和擴展。而且,它能夠有效地避免狀態(tài)判斷的遺漏和錯誤,提高代碼的健壯性。

二、Spring 狀態(tài)機:Java 開發(fā)者的狀態(tài)管理神器

Spring 狀態(tài)機是 Spring 框架提供的一個用于構建狀態(tài)機的模塊,它基于狀態(tài)模式和責任鏈模式,能夠方便地在 Java 應用中實現(xiàn)狀態(tài)機。Spring 狀態(tài)機支持多種狀態(tài)機模型,包括 UML 狀態(tài)機和簡單狀態(tài)機,我們可以根據(jù)具體的業(yè)務需求選擇合適的模型。

(一)Spring 狀態(tài)機的核心概念

狀態(tài)(State)

在 Spring 狀態(tài)機中,狀態(tài)可以分為簡單狀態(tài)和復合狀態(tài)。簡單狀態(tài)就是一個獨立的狀態(tài),比如訂單的創(chuàng)建狀態(tài);復合狀態(tài)可以包含子狀態(tài),比如訂單的處理中狀態(tài)可以包含支付中、發(fā)貨中等子狀態(tài)。我們可以通過枚舉類型來定義狀態(tài),例如:

public enum OrderState {
    CREATED, PAID, SHIPPED, DELIVERED, CANCELED, REFUNDED
}

事件(Event)

事件是觸發(fā)狀態(tài)轉換的原因,同樣可以用枚舉類型來定義,例如:

public enum OrderEvent {
    PAY, SHIP, DELIVER, CANCEL, REFUND
}

轉換(Transition)

轉換定義了從源狀態(tài)到目標狀態(tài)的映射,以及觸發(fā)轉換的事件和可能的守衛(wèi)條件、動作。在 Spring 狀態(tài)機中,我們可以通過配置來定義轉換規(guī)則。

動作(Action)

動作可以在狀態(tài)轉換的不同階段執(zhí)行,比如在事件觸發(fā)時、狀態(tài)轉換前、狀態(tài)轉換后等。我們可以自定義動作類,實現(xiàn) Action 接口,然后在配置中指定動作的執(zhí)行時機。

守衛(wèi)條件(Guard)

守衛(wèi)條件用于判斷事件是否能夠觸發(fā)狀態(tài)轉換,它是一個實現(xiàn)了 Guard 接口的類,返回一個布爾值。例如,只有當訂單未被取消時,才能進行發(fā)貨操作。

(二)Spring 狀態(tài)機的優(yōu)勢

代碼結構清晰

使用 Spring 狀態(tài)機,我們可以將狀態(tài)邏輯從業(yè)務代碼中分離出來,通過配置的方式定義狀態(tài)轉換規(guī)則,使得代碼更加簡潔明了,易于理解和維護。

易于擴展

當業(yè)務需求發(fā)生變化,需要新增狀態(tài)或修改狀態(tài)轉換規(guī)則時,只需修改狀態(tài)機的配置,而無需修改大量的業(yè)務代碼,降低了代碼的修改成本。

支持復雜狀態(tài)邏輯

Spring 狀態(tài)機支持復合狀態(tài)、子狀態(tài)機等高級特性,能夠處理復雜的業(yè)務狀態(tài)邏輯,比如工作流、有限狀態(tài)自動機等。

與 Spring 生態(tài)集成良好

作為 Spring 框架的一部分,Spring 狀態(tài)機可以無縫集成 Spring 的其他模塊,比如 Spring Boot、Spring Data 等,方便我們構建完整的應用系統(tǒng)。

三、手把手教你用 Spring 狀態(tài)機玩轉訂單狀態(tài)管理

接下來,咱就以訂單狀態(tài)管理為例,一步步教你如何使用 Spring 狀態(tài)機來實現(xiàn)優(yōu)雅的狀態(tài)邏輯處理。

(一)引入依賴

首先,我們需要在項目中引入 Spring 狀態(tài)機的依賴。如果使用 Spring Boot,只需在 pom.xml 中添加以下依賴:

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-config</artifactId>
</dependency>

(二)定義狀態(tài)和事件

我們已經(jīng)在前面定義了訂單的狀態(tài)枚舉 OrderState 和事件枚舉 OrderEvent,這里就不再重復了。

(三)配置狀態(tài)機

Spring 狀態(tài)機的配置可以通過 Java 配置類來實現(xiàn),我們需要創(chuàng)建一個配置類,繼承 StateMachineConfigurerAdapter,并覆蓋相關的方法來定義狀態(tài)機的狀態(tài)、轉換、動作和守衛(wèi)條件等。

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {
    // 定義狀態(tài)
    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
        states
           .withStates()
               .initial(OrderState.CREATED) // 初始狀態(tài)
               .states(EnumSet.allOf(OrderState.class));
    }
    // 定義轉換
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
        transitions
           .withExternal() // 外部轉換,會改變狀態(tài)
               .source(OrderState.CREATED) // 源狀態(tài)
               .target(OrderState.PAID) // 目標狀態(tài)
               .event(OrderEvent.PAY) // 觸發(fā)事件
               .action(payAction()) // 執(zhí)行的動作
               .guard(payGuard()) // 守衛(wèi)條件
           .and()
           .withExternal()
               .source(OrderState.PAID)
               .target(OrderState.SHIPPED)
               .event(OrderEvent.SHIP)
               .action(shipAction())
           .and()
           .withExternal()
               .source(OrderState.SHIPPED)
               .target(OrderState.DELIVERED)
               .event(OrderEvent.DELIVER)
               .action(deliverAction())
           .and()
           .withExternal()
               .source(OrderState.CREATED)
               .target(OrderState.CANCELED)
               .event(OrderEvent.CANCEL)
               .action(cancelAction())
           .and()
           .withExternal()
               .source(OrderState.PAID)
               .target(OrderState.REFUNDED)
               .event(OrderEvent.REFUND)
               .action(refundAction());
    }
    // 定義動作
    @Bean
    public Action<OrderState, OrderEvent> payAction() {
        return new Action<OrderState, OrderEvent>() {
            @Override
            public void execute(StateContext<OrderState, OrderEvent> context) {
                // 處理支付動作,比如更新訂單支付時間、調(diào)用支付接口等
                System.out.println("執(zhí)行支付動作");
                Order order = context.getMessage().getHeaders().get("order", Order.class);
                order.setStatus(OrderState.PAID);
                order.setPaymentTime(new Date());
                // 這里可以添加具體的業(yè)務邏輯
            }
        };
    }
    @Bean
    public Action<OrderState, OrderEvent> shipAction() {
        return context -> {
            // 處理發(fā)貨動作,比如生成物流單號、更新發(fā)貨時間等
            System.out.println("執(zhí)行發(fā)貨動作");
            Order order = context.getMessage().getHeaders().get("order", Order.class);
            order.setStatus(OrderState.SHIPPED);
            order.setShipTime(new Date());
            // 這里可以添加具體的業(yè)務邏輯
        };
    }
    // 定義守衛(wèi)條件
    @Bean
    public Guard<OrderState, OrderEvent> payGuard() {
        return context -> {
            // 判斷訂單金額是否大于 0,只有金額大于 0 才能支付
            Order order = context.getMessage().getHeaders().get("order", Order.class);
            return order.getAmount() > 0;
        };
    }
}

在上面的配置中,我們首先定義了狀態(tài),指定了初始狀態(tài)為 CREATED,并包含了所有的訂單狀態(tài)。然后定義了轉換規(guī)則,每個轉換都指定了源狀態(tài)、目標狀態(tài)、觸發(fā)事件、動作和守衛(wèi)條件(可選)。動作和守衛(wèi)條件通過 Bean 的方式定義,方便重用和測試。

(四)使用狀態(tài)機

配置好狀態(tài)機之后,我們就可以在業(yè)務代碼中使用它了。首先,需要注入 StateMachine 對象:

@Autowired
private StateMachine<OrderState, OrderEvent> orderStateMachine;

然后,在處理事件時,創(chuàng)建消息對象,并將訂單對象作為參數(shù)傳遞給狀態(tài)機:

public void processEvent(Order order, OrderEvent event) {
    // 創(chuàng)建消息,將訂單對象作為參數(shù)
    Message<OrderEvent> message = MessageBuilder.withPayload(event)
       .setHeader("order", order)
       .build();
    // 發(fā)送事件給狀態(tài)機
    orderStateMachine.sendEvent(message);
}

當狀態(tài)機接收到事件后,會根據(jù)配置的轉換規(guī)則進行狀態(tài)轉換,并執(zhí)行相應的動作和守衛(wèi)條件。

(五)狀態(tài)機監(jiān)聽器

為了更好地監(jiān)控狀態(tài)機的狀態(tài)變化,我們可以添加監(jiān)聽器,監(jiān)聽狀態(tài)的進入、退出和轉換等事件。例如:

@Configuration
public class OrderStateMachineListenerConfig {
    @Autowired
    public void configure(StateMachineFactory<OrderState, OrderEvent> factory) {
        factory.getStateMachine().addStateListener(new StateListener<OrderState, OrderEvent>() {
            @Override
            public void stateChanged(State<OrderState, OrderEvent> from, State<OrderState, OrderEvent> to) {
                // 狀態(tài)發(fā)生變化時觸發(fā)
                System.out.println("狀態(tài)從 " + from.getId() + " 轉換到 " + to.getId());
            }
        });
        factory.getStateMachine().addTransitionListener(new TransitionListener<OrderState, OrderEvent>() {
            @Override
            public void transitionStarted(Transition<OrderState, OrderEvent> transition) {
                // 轉換開始時觸發(fā)
                System.out.println("轉換開始:" + transition.getSource().getId() + " -> " + transition.getTarget().getId());
            }
            @Override
            public void transitionEnded(Transition<OrderState, OrderEvent> transition) {
                // 轉換結束時觸發(fā)
                System.out.println("轉換結束:" + transition.getSource().getId() + " -> " + transition.getTarget().getId());
            }
        });
    }
}

通過監(jiān)聽器,我們可以在狀態(tài)轉換的各個階段執(zhí)行一些額外的操作,比如記錄日志、發(fā)送通知等。

四、Spring 狀態(tài)機進階:處理復雜業(yè)務場景

(一)復合狀態(tài)和子狀態(tài)機

當業(yè)務場景比較復雜,狀態(tài)之間存在層次關系時,我們可以使用復合狀態(tài)和子狀態(tài)機。例如,訂單在支付過程中可能有支付中、支付成功、支付失敗等子狀態(tài),我們可以將支付過程定義為一個復合狀態(tài),其中包含這些子狀態(tài)。

public enum OrderState {
    CREATED,
    PAYING(CompositeState.PAYMENT), // 復合狀態(tài)
    PAID,
    PAYMENT_FAILED,
    SHIPPED,
    DELIVERED,
    CANCELED,
    REFUNDED
}

// 復合狀態(tài)枚舉
publicenum CompositeState {
    PAYMENT
}

在配置狀態(tài)機時,我們可以定義復合狀態(tài)及其子狀態(tài):

@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
    states
       .withStates()
           .initial(OrderState.CREATED)
           .states(EnumSet.allOf(OrderState.class))
           .and()
           .withCompositeStates()
               .withState(OrderState.PAYING, CompositeState.PAYMENT)
               .withStates(CompositeState.PAYMENT)
                   .initial(OrderState.PAYING)
                   .states(EnumSet.of(OrderState.PAYING, OrderState.PAID, OrderState.PAYMENT_FAILED));
}

(二)持久化狀態(tài)機上下文

在實際應用中,我們可能需要將狀態(tài)機的上下文(比如訂單對象)持久化,以便在應用重啟后能夠恢復狀態(tài)機的狀態(tài)。Spring 狀態(tài)機支持將狀態(tài)機的上下文持久化到數(shù)據(jù)庫或其他存儲介質(zhì)中,我們可以通過實現(xiàn) StateMachinePersist 接口來實現(xiàn)自定義的持久化邏輯。

(三)與外部系統(tǒng)交互

在狀態(tài)轉換過程中,可能需要與外部系統(tǒng)進行交互,比如調(diào)用支付接口、物流接口等。這時候,我們可以在動作中使用 Spring 的 RestTemplate 或其他客戶端來發(fā)起遠程調(diào)用,并處理調(diào)用結果。

@Bean
public Action<OrderState, OrderEvent> payAction() {
    return context -> {
        Order order = context.getMessage().getHeaders().get("order", Order.class);
        // 調(diào)用支付接口
        PaymentResponse response = restTemplate.postForObject(paymentUrl, order, PaymentResponse.class);
        if (response.isSuccess()) {
            order.setStatus(OrderState.PAID);
            order.setPaymentTime(new Date());
        } else {
            // 處理支付失敗,轉換到支付失敗狀態(tài)
            context.getStateMachine().transition(OrderEvent.PAYMENT_FAILED);
        }
    };
}

五、踩坑指南:使用 Spring 狀態(tài)機常見問題及解決辦法

(一)狀態(tài)轉換不生效

如果發(fā)現(xiàn)發(fā)送事件后狀態(tài)沒有轉換,首先要檢查配置的轉換規(guī)則是否正確,源狀態(tài)、目標狀態(tài)和事件是否匹配。其次,檢查守衛(wèi)條件是否返回 true,如果守衛(wèi)條件不滿足,轉換不會發(fā)生。另外,還要注意狀態(tài)機是否已經(jīng)啟動,在 Spring Boot 中,狀態(tài)機默認是自動啟動的,但如果在配置中關閉了自動啟動,需要手動調(diào)用 stateMachine.start() 方法。

(二)動作執(zhí)行順序問題

有時候,我們可能需要在狀態(tài)轉換的不同階段執(zhí)行不同的動作,比如在狀態(tài)轉換前執(zhí)行一些準備工作,在轉換后執(zhí)行一些清理工作。Spring 狀態(tài)機支持在轉換中定義多個動作,動作的執(zhí)行順序按照定義的順序進行。如果需要更精細地控制動作的執(zhí)行時機,可以使用 Action 接口的不同實現(xiàn),或者在配置中使用 beforeAction 和 afterAction 方法。

(三)狀態(tài)機上下文丟失

在使用狀態(tài)機時,上下文對象(比如訂單對象)通常是通過消息的頭部傳遞的。如果在狀態(tài)轉換過程中,上下文對象沒有正確傳遞,可能會導致動作或守衛(wèi)條件無法獲取到所需的數(shù)據(jù)。因此,在發(fā)送消息時,一定要確保上下文對象被正確設置到消息的頭部,并且在動作和守衛(wèi)條件中正確獲取。

(四)復雜狀態(tài)機調(diào)試困難

當狀態(tài)機配置比較復雜時,調(diào)試可能會比較困難。這時候,我們可以利用 Spring 狀態(tài)機提供的調(diào)試工具,比如打印狀態(tài)機的狀態(tài)和轉換信息,或者使用斷點調(diào)試來跟蹤狀態(tài)轉換的過程。另外,合理使用監(jiān)聽器來記錄狀態(tài)轉換的日志,也能幫助我們快速定位問題。

六、總結:早用早受益,代碼優(yōu)雅不是夢

說了這么多,相信大家對 Spring 狀態(tài)機已經(jīng)有了一個比較清晰的認識了。使用 Spring 狀態(tài)機,我們可以將復雜的狀態(tài)邏輯從業(yè)務代碼中分離出來,通過配置的方式進行管理,讓代碼更加簡潔、優(yōu)雅、易維護。再也不用為了處理狀態(tài)邏輯而寫一堆惡心人的 if-else 了,媽媽再也不用擔心我的代碼會因為狀態(tài)判斷而出現(xiàn) bug 了。

當然,Spring 狀態(tài)機還有很多高級特性和應用場景等待我們?nèi)ヌ剿鳎热绻ぷ髁饕妗⒂邢逘顟B(tài)自動機等。只要我們合理運用,它就能成為我們開發(fā)過程中的得力助手,讓我們的代碼質(zhì)量更上一層樓。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2020-10-15 10:38:35

C語言狀態(tài)模型

2023-03-06 07:35:30

狀態(tài)機工具訂單狀態(tài)

2020-12-02 13:33:58

函數(shù)指針編程語言

2021-07-08 09:15:20

單片機編程狀態(tài)機編程語言

2010-06-18 12:38:38

UML狀態(tài)機視圖

2013-09-03 09:57:43

JavaScript有限狀態(tài)機

2010-06-18 13:25:44

UML狀態(tài)機視圖

2024-10-10 17:46:06

2021-06-05 05:11:52

代碼狀態(tài)機邏輯

2022-03-06 19:57:50

狀態(tài)機easyfsm項目

2010-07-08 13:03:31

UML狀態(tài)機圖

2011-06-24 16:09:24

Qt 動畫 狀態(tài)機

2024-04-16 09:21:59

Spring流轉狀態(tài)數(shù)據(jù)狀態(tài)處理

2020-03-27 10:50:29

DSL 狀態(tài)機工具

2021-12-28 08:24:18

函數(shù)指針有限狀態(tài)機編程

2010-07-12 15:00:56

UML狀態(tài)機視圖

2021-08-19 09:00:00

微服務開發(fā)架構

2021-04-29 09:31:05

前端開發(fā)技術

2010-02-24 09:32:38

Visual Stud

2010-06-18 13:15:07

UML狀態(tài)機圖
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲综合无码一区二区 | 9久9久9久女女女九九九一九 | 中文字幕在线精品 | www.久草| 日韩国产欧美一区 | 成人精品一区二区 | 国产一区二区自拍 | 色久影院 | 性国产xxxx乳高跟 | 国产视频在线一区二区 | 一区二区三区四区在线视频 | 一区二区免费在线视频 | 亚洲国产精品久久久久婷婷老年 | 国产探花 | 天堂在线中文 | 91一区二区三区 | 国产片淫级awww | 国产精品成人一区二区三区夜夜夜 | 亚洲人成人网 | 毛片的网址 | 欧美一区视频 | 一级毛片免费 | 欧美在线观看一区 | 久久久www成人免费精品张筱雨 | 日韩在线一区二区三区 | 国产精品日产欧美久久久久 | 97精品超碰一区二区三区 | 亚洲在线视频 | 极品一区| 最新中文字幕第一页视频 | 国产精品99视频 | 亚洲人在线观看视频 | 一区二区精品 | 中文字幕av一区 | 中国美女av| 911精品国产 | 亚洲欧美一区二区三区情侣bbw | 国产日韩精品在线 | 亚洲综合三区 | 国产情侣啪啪 | 天天夜夜人人 |