1.什么是statemachine?
Spring Statemachine 是應用程序開發人員在 Spring 應用程序中使用狀態機概念的框架,從設計層面分析:狀態機目的是解決復雜的狀態管理流程,保證程序單一原則和開閉原則;業務角度分析:狀態機應有初始化狀態、加載所有已有的狀態、事件、轉移、動作、觸發下一個狀態為驅動,并解決了業務邏輯與狀態管理之間的強耦合。
Spring Statemachine?提供以下功能:
- 易于使用的平面(一級)狀態機,適用于簡單的用例。
- 分層狀態機結構,以簡化復雜的狀態配置。
- 狀態機區域提供更復雜的狀態配置。
- 觸發器、轉換、守衛和動作的使用。
- 類型安全的配置適配器。
- 狀態機事件監聽器。
- Spring IoC 集成將 bean 與狀態機相關聯。
Spring Statemachine 原理
Spring狀態機建立在有限狀態機(FSM)的概念之上,提供了一種簡潔且靈活的方式來定義、管理和執行狀態機。它將狀態定義為Java對象,并通過配置來定義狀態之間的轉換規則。狀態轉換通常由外部事件觸發,我們可以根據業務邏輯定義不同的事件類型,并與狀態轉換關聯。Spring狀態機還提供了狀態監聽器,用于在狀態變化時執行特定的邏輯。同時,狀態機的狀態可以持久化到數據庫或其他存儲介質中,以便在系統重啟或故障恢復時保持狀態的一致性。 Spring狀態機核心主要包括以下三個關鍵元素:
- 狀態(State):定義了系統可能處于的各個狀態,如訂單狀態中的待支付、已支付等。
- 轉換(Transition):描述了在何種條件下,當接收到特定事件時,系統可以從一個狀態轉移到另一個狀態。例如,接收到“支付成功”事件時,訂單狀態從“待支付”轉變為“已支付”。
- 事件(Event):觸發狀態轉換的動作或者消息,它是引起狀態機從當前狀態遷移到新狀態的原因。
接下來,我們將上述狀態模式中關于訂單狀態的示例轉換為狀態機實現。 ?
2.代碼工程
實驗目標:訂單狀態的示例轉換為狀態機實現。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>Statemachine</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-core</artifactId><version>2.0.0.RELEASE</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>
定義狀態機的狀態以及事件類型
定義狀態(States): 狀態是狀態機的核心組成單元,代表了系統或對象在某一時刻可能存在的條件或模式。在狀態機中,每一個狀態都是系統可能處于的一種明確的條件或階段。例如,在一個簡單的咖啡機狀態機中,可能有的狀態包括“待機”、“磨豆”、“沖泡”和“完成”。每個狀態都是獨一無二的,且在任何給定時間,系統只能處于其中一個狀態。?定義轉換(Transitions): 轉換則是指狀態之間的轉變過程,它是狀態機模型動態性的體現。當一個外部事件(如用戶按下按鈕、接收到信號、滿足特定條件等)觸發時,狀態機會從當前狀態轉移到另一個狀態。在定義轉換時,需要指出觸發轉換的事件(Event
)以及事件發生時系統的響應,即從哪個狀態(Source State
)轉到哪個狀態(Target State
)。
package com.et.statemachine.state;/*** @description:order status*/
public enum OrderStatusChangeEventEnum {PAYED,DELIVERY,RECEIVED;
}
package com.et.statemachine.state;/*** @description: order status*/
public enum OrderStatusEnum {WAIT_PAYMENT,WAIT_DELIVER,WAIT_RECEIVE,FINISH;
}
定義狀態機以及狀態流轉規則
狀態機配置類是在使用Spring State Machine
或其他狀態機框架時的一個重要步驟,這個類主要用于定義狀態機的核心結構,包括狀態(states
)、事件(events
)、狀態之間的轉換規則(transitions
),以及可能的狀態遷移動作和決策邏輯。 在Spring State Machine
中,創建狀態機配置類通常是通過繼承StateMachineConfigurerAdapter
類來實現的。這個適配器類提供了幾個模板方法,允許開發者重寫它們來配置狀態機的各種組成部分:
- 配置狀態(
configureStates(StateMachineStateConfigurer)
): 在這個方法中,開發者定義狀態機中所有的狀態,包括初始狀態(initial state
)和結束狀態(final/terminal states
)。例如,定義狀態A、B、C,并指定狀態A作為初始狀態。 - 配置轉換(
configureTransitions(StateMachineTransitionConfigurer)
): 在這里,開發者描述狀態之間的轉換規則,也就是當某個事件(event
)發生時,狀態機應如何從一個狀態轉移到另一個狀態。例如,當事件X發生時,狀態機從狀態A轉移到狀態B。 - 配置初始狀態(
configureInitialState(ConfigurableStateMachineInitializer)
): 如果需要顯式指定狀態機啟動時的初始狀態,可以在該方法中設置。
package com.et.statemachine.config;import com.et.statemachine.state.OrderStatusChangeEventEnum;
import com.et.statemachine.state.OrderStatusEnum;
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;/*** @description: order statemachine*/
@Configuration
@EnableStateMachine
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum> {/*** configure state*/@Overridepublic void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception {states.withStates().initial(OrderStatusEnum.WAIT_PAYMENT).end(OrderStatusEnum.FINISH).states(EnumSet.allOf(OrderStatusEnum.class));}/*** configure state transient with event*/@Overridepublic void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> transitions) throws Exception {transitions.withExternal().source(OrderStatusEnum.WAIT_PAYMENT).target(OrderStatusEnum.WAIT_DELIVER).event(OrderStatusChangeEventEnum.PAYED).and().withExternal().source(OrderStatusEnum.WAIT_DELIVER).target(OrderStatusEnum.WAIT_RECEIVE).event(OrderStatusChangeEventEnum.DELIVERY).and().withExternal().source(OrderStatusEnum.WAIT_RECEIVE).target(OrderStatusEnum.FINISH).event(OrderStatusChangeEventEnum.RECEIVED);}
}
定義狀態機監聽器
狀態機監聽器(State Machine Listener
)是一種組件,它可以監聽并響應狀態機在運行過程中的各種事件,例如狀態變遷、進入或退出狀態、轉換被拒絕等。 在Spring Statemachine
中,監聽器可以通過實現StateMachineListener
接口來定義。該接口提供了一系列回調方法,如transitionTriggered
、stateEntered
、stateExited
等,當狀態機觸發轉換、進入新狀態或離開舊狀態時,這些方法會被調用。同時,我們也可以通過注解實現監聽器。注解方式可以在類的方法上直接聲明該方法應該在何種狀態下被調用,簡化監聽器的編寫和配置。例如@OnTransition
,@OnTransitionEnd
,@OnTransitionStart
等
package com.et.statemachine.listener;import com.et.statemachine.state.Order;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;/*** @description: state listener*/
@Component
@WithStateMachine
@Transactional
public class OrderStatusListener {@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")public boolean payTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);System.out.println("pay,feedback by statemachine:" + message.getHeaders().toString());return true;}@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")public boolean deliverTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);System.out.println("deliver,feedback by statemachine:" + message.getHeaders().toString());return true;}@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")public boolean receiveTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.FINISH);System.out.println("receive,feedback by statemachine:" + message.getHeaders().toString());return true;}}
service
package com.et.statemachine.service;import com.et.statemachine.state.Order;import java.util.Map;/*** @author liuhaihua* @version 1.0* @ClassName OrderService* @Description todo* @date 2024年05月27日 15:15*/public interface OrderService {Order create();Order pay(long id);Order deliver(long id);Order receive(long id);Map<Long, Order> getOrders();
}
package com.et.statemachine.service;import com.et.statemachine.state.Order;
import com.et.statemachine.state.OrderStatusChangeEventEnum;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;/*** @description: order service*/
@Service
public class OrderServiceImpl implements OrderService {@Resourceprivate StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;private long id = 1L;private Map<Long, Order> orders = new ConcurrentHashMap<>();@Overridepublic Order create() {Order order = new Order();order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);order.setOrderId(id++);orders.put(order.getOrderId(), order);System.out.println("order create success:" + order.toString());return order;}@Overridepublic Order pay(long id) {Order order = orders.get(id);System.out.println("try to pay,order no:" + id);Message message = MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED).setHeader("order", order).build();if (!sendEvent(message)) {System.out.println(" pay fail, error,order no:" + id);}return orders.get(id);}@Overridepublic Order deliver(long id) {Order order = orders.get(id);System.out.println(" try to deliver,order no:" + id);if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY).setHeader("order", order).build())) {System.out.println(" deliver fail,error,order no:" + id);}return orders.get(id);}@Overridepublic Order receive(long id) {Order order = orders.get(id);System.out.println(" try to receiver,order no:" + id);if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED).setHeader("order", order).build())) {System.out.println(" deliver fail,error,order no:" + id);}return orders.get(id);}@Overridepublic Map<Long, Order> getOrders() {return orders;}/*** send transient event* @param message* @return*/private synchronized boolean sendEvent(Message<OrderStatusChangeEventEnum> message) {boolean result = false;try {orderStateMachine.start();result = orderStateMachine.sendEvent(message);} catch (Exception e) {e.printStackTrace();} finally {if (Objects.nonNull(message)) {Order order = (Order) message.getHeaders().get("order");if (Objects.nonNull(order) && Objects.equals(order.getOrderStatus(), OrderStatusEnum.FINISH)) {orderStateMachine.stop();}}}return result;}
}
controller
package com.et.statemachine.controller;import com.et.statemachine.service.OrderService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;@RestController
public class HelloWorldController {@RequestMapping("/hello")public Map<String, Object> showHelloWorld(){Map<String, Object> map = new HashMap<>();map.put("msg", "HelloWorld");return map;}@Resourceprivate OrderService orderService;@RequestMapping("/testOrderStatusChange")public String testOrderStatusChange(){orderService.create();orderService.create();orderService.pay(1L);orderService.deliver(1L);orderService.receive(1L);orderService.pay(2L);orderService.deliver(2L);orderService.receive(2L);System.out.println("all orders:" + orderService.getOrders());return "success";}}
以上只是一些關鍵代碼,所有代碼請參見下面代碼倉庫
代碼倉庫
- GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.
3.測試
啟動Spring Boot應用
測試狀態機
訪問http://127.0.0.1:8088/testOrderStatusChange,查看控制臺輸出
order create success:Order(orderId=1, orderStatus=WAIT_PAYMENT)
order create success:Order(orderId=2, orderStatus=WAIT_PAYMENT)
try to pay,order no:1
2024-05-27 22:58:14.208 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.209 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / WAIT_PAYMENT / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
pay,feedback by statemachine:{order=Order(orderId=1, orderStatus=WAIT_DELIVER), id=fc2c0720-e6ba-9bf8-d359-72a6c61b4186, timestamp=1716821894196}try to deliver,order no:1
deliver,feedback by statemachine:{order=Order(orderId=1, orderStatus=WAIT_RECEIVE), id=e743d376-22e1-bfc3-1c62-7131ff1bf7c1, timestamp=1716821894227}try to receiver,order no:1
receive,feedback by statemachine:{order=Order(orderId=1, orderStatus=FINISH), id=652167b8-e74f-bde2-62f7-94bdbb5bad7e, timestamp=1716821894229}
2024-05-27 22:58:14.230 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.230 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
try to pay,order no:2
2024-05-27 22:58:14.231 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.231 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / WAIT_PAYMENT / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
pay,feedback by statemachine:{order=Order(orderId=2, orderStatus=WAIT_DELIVER), id=d331fa76-8a28-aaa7-6257-a9404c2084d6, timestamp=1716821894230}try to deliver,order no:2
deliver,feedback by statemachine:{order=Order(orderId=2, orderStatus=WAIT_RECEIVE), id=4e930443-6b04-fd86-6740-5631db2aea1d, timestamp=1716821894232}try to receiver,order no:2
receive,feedback by statemachine:{order=Order(orderId=2, orderStatus=FINISH), id=6473cc9e-5cd9-0de5-12c8-7d51dd3f9da6, timestamp=1716821894233}
2024-05-27 22:58:14.234 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.234 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
all orders:{1=Order(orderId=1, orderStatus=FINISH), 2=Order(orderId=2, orderStatus=FINISH)}
4.引用
- Spring State Machine
- Spring Boot集成statemachine快速入門demo | Harries Blog?
? ?