Spring狀態(tài)機(jī) Statemachine使用小結(jié)
1. 狀態(tài)機(jī)簡(jiǎn)介
狀態(tài)機(jī)(State Machine)是一種描述系統(tǒng)行為的數(shù)學(xué)模型,核心思想是將系統(tǒng)抽象為有限個(gè)狀態(tài),并通過(guò)狀態(tài)轉(zhuǎn)移規(guī)則定義系統(tǒng)如何響應(yīng)外部事件。它由一系列狀態(tài)(States)、轉(zhuǎn)換(Transitions)、事件(Events)、衛(wèi)兵(Guards)和動(dòng)作(Actions)組成。在軟件開發(fā)中,狀態(tài)機(jī)常用于控制程序的流程和狀態(tài)變化。
其核心優(yōu)勢(shì)在于: 通過(guò)清晰明確的狀態(tài)定義、自動(dòng)狀態(tài)更新,將復(fù)雜的狀態(tài)流轉(zhuǎn)邏輯結(jié)構(gòu)化。從而使得業(yè)務(wù)邏輯與狀態(tài)解耦,減少了自己維護(hù)狀態(tài)產(chǎn)生的大量if-else,顯著提升代碼可維護(hù)性和擴(kuò)展性??。
2. 核心組件介紹
2.1 流轉(zhuǎn)邏輯相關(guān):
組件? | 處理邏輯類型? |
State枚舉 | 枚舉對(duì)象所有的狀態(tài)(流程圖的節(jié)點(diǎn)) |
Events枚舉 | 枚舉所有引起兩個(gè)狀態(tài)之間切換的事件(流程圖的箭頭) |
Configurer配置類 | 組裝State和Event的轉(zhuǎn)換關(guān)系 (相當(dāng)于流程圖) |
| StateMachine狀態(tài)機(jī) | 一個(gè)狀態(tài)機(jī)表征一個(gè)對(duì)象實(shí)體,它擁有狀態(tài),并通過(guò)event的驅(qū)動(dòng)來(lái)按照Configurer的配置進(jìn)行狀態(tài)變化,同時(shí)執(zhí)行配置中指定的guard、action和listener |
2.2 業(yè)務(wù)邏輯組件:
(具體業(yè)務(wù)開發(fā)在這里寫,由框架負(fù)責(zé)調(diào)度)
組件? | 處理邏輯類型? | 是否阻塞遷移? |
Guard? | 純校驗(yàn)邏輯 (金額校驗(yàn)/渠道檢查) | 是,失敗則中斷遷移 |
Action? | 核心業(yè)務(wù)操作 (支付執(zhí)行/庫(kù)存扣減) | 是,異常導(dǎo)致遷移失敗 |
Listener? | 后續(xù)操作響應(yīng) (通知/日志/監(jiān)控) | 否,異步執(zhí)行(也可同步) |
2.3 流程示例圖

3. 示例代碼
3.1 依賴引入
<!--狀態(tài)機(jī)--> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-starter</artifactId> <version>3.2.0</version> </dependency> <!-- redis持久化狀態(tài)機(jī)--非必需組件,也可以用文件或數(shù)據(jù)庫(kù)做持久化 --> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-redis</artifactId> <version>1.2.9.RELEASE</version> </dependency>
3.2 狀態(tài)枚舉定義
public enum OrderStatus {
// 待支付,待發(fā)貨,待收貨,已完成
WAIT_PAYMENT(1, "待支付"),
WAIT_DELIVER(2, "待發(fā)貨"),
WAIT_RECEIVE(3, "待收貨"),
FINISH(4, "已完成");
private Integer key;
private String desc;
OrderStatus(Integer key, String desc) {
this.key = key;
this.desc = desc;
}
public Integer getKey() {
return key;
}
public String getDesc() {
return desc;
}
}3.3 事件枚舉定義
public enum OrderStatusChangeEvent {
// 支付,發(fā)貨,確認(rèn)收貨
PAYED, DELIVERY, RECEIVED,UNPAY;
/**
* 獲取下一個(gè)事件枚舉
*
* @return 下一個(gè)事件枚舉
*/
public OrderStatusChangeEvent getNextEvent() {
switch (this) {
case UNPAY:
return PAYED;
case PAYED:
return DELIVERY;
case DELIVERY:
return RECEIVED;
case RECEIVED:
// 如果是最后一個(gè)事件,則返回null
return null;
default:
// 如果枚舉值不在預(yù)期范圍內(nèi),則拋出異常
throw new IllegalArgumentException("不支持的事件枚舉值");
}
}
}3.4 配置類
@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
@Autowired
private OrderGuard orderGuard;
@Autowired
private OrderAction orderAction;
/**
* 配置狀態(tài)
*
* @param states
* @throws Exception
*/
public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
states
.withStates()
.initial(OrderStatus.WAIT_PAYMENT)
.states(EnumSet.allOf(OrderStatus.class));
}
/**
* 配置狀態(tài)轉(zhuǎn)換事件關(guān)系
*
* @param transitions
* @throws Exception
*/
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
transitions
//支付事件:待支付-》待發(fā)貨
.withExternal()
.source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER)
.event(OrderStatusChangeEvent.PAYED)
.guard(orderGuard)
.action(orderAction)
.and()
//發(fā)貨事件:待發(fā)貨-》待收貨
.withExternal()
.source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE)
.event(OrderStatusChangeEvent.DELIVERY)
.guard(orderGuard)
.action(orderAction)
.and()
//收貨事件:待收貨-》已完成
.withExternal()
.source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH)
.event(OrderStatusChangeEvent.RECEIVED)
.guard(orderGuard)
.action(orderAction)
.and()
.withExternal()
.source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_PAYMENT)
.event(OrderStatusChangeEvent.UNPAY)
.guard(orderGuard)
.action(orderAction);
}
}3.5 Guard--存放業(yè)務(wù)檢查邏輯
業(yè)務(wù)開發(fā)只用關(guān)心實(shí)現(xiàn)這個(gè)Guard接口不用關(guān)心執(zhí)行調(diào)度和狀態(tài)維護(hù)。
@Component
public class OrderGuard implements Guard<OrderStatus, OrderStatusChangeEvent> {
@Override
public boolean evaluate(StateContext<OrderStatus, OrderStatusChangeEvent> context) {
Order order = context.getMessage().getHeaders().get("order", Order.class);
// ...自定義業(yè)務(wù)檢查邏輯
return true;
}
}3.6 Action---存放業(yè)務(wù)執(zhí)行邏輯
業(yè)務(wù)開發(fā)只用關(guān)心實(shí)現(xiàn)這個(gè)Action接口不用關(guān)心執(zhí)行調(diào)度和狀態(tài)維護(hù)
@Slf4j
@Component
public class OrderAction implements Action<OrderStatus,OrderStatusChangeEvent> {
@Resource
private OrderMapper orderMapper;
@Override
public void execute(StateContext<OrderStatus, OrderStatusChangeEvent> stateContext) {
try {
Order order = stateContext.getMessage().getHeaders().get("order", Order.class);
State<OrderStatus, OrderStatusChangeEvent> target = stateContext.getTarget();
order.setStatus(target.getId().getKey());
log.info("orderAction:{}", order);
// 自定義業(yè)務(wù)執(zhí)行邏輯
} catch (Exception e) {
log.error("訂單狀態(tài)機(jī)執(zhí)行異常", e);
stateContext.getStateMachine().setStateMachineError(new RuntimeException("自定義錯(cuò)誤"));
throw e;
}
}
}3.7 核心控制類
對(duì)并發(fā)有要求的使用工廠方法,每個(gè)訂單都單獨(dú)使用一個(gè)狀態(tài)機(jī)表達(dá)
@Service("orderService")
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Autowired
private StateMachineFactory<OrderStatus, OrderStatusChangeEvent> stateMachineFactory;
@Resource
private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineRedisPersister;
@Resource
private OrderMapper orderMapper;
@Autowired
private RedisUtil redisUtil;
@Autowired
OrderStateListener orderStateListener;
/**
* 創(chuàng)建訂單
*
* @param order
* @return
*/
@DS("oracle-xxx")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Order create(Order order) {
order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());
orderMapper.insert(order);
return order;
}
/**
* 對(duì)訂單進(jìn)行支付
*
* @param id
* @return
*/
@DS("oracle-xxx")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean pay(Long id) {
Order order = orderMapper.selectById(id);
log.info("線程名稱:{},嘗試支付,訂單號(hào):{}" ,Thread.currentThread().getName() , id);
return autoSendEvent(OrderStatusChangeEvent.PAYED, order);
}
/**
* 發(fā)送訂單狀態(tài)轉(zhuǎn)換事件
* synchronized修飾保證這個(gè)方法是線程安全的
*
* @param changeEvent
* @param order
* @return
*/
private boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order) {
boolean result = false;
StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine = null;
try {
// 工廠方法生成狀態(tài)機(jī)
orderStateMachine = stateMachineFactory.getStateMachine(order.getId());
orderStateMachine.addStateListener(orderStateListener);
//啟動(dòng)狀態(tài)機(jī)
orderStateMachine.start();
//嘗試恢復(fù)狀態(tài)機(jī)狀態(tài)
stateMachineRedisPersister.restore(orderStateMachine, String.valueOf(order.getId()));
Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
result = orderStateMachine.sendEvent(message);
boolean hasError = orderStateMachine.hasStateMachineError();
//持久化狀態(tài)機(jī)狀態(tài)
if(hasError){
return !hasError;
}
stateMachineRedisPersister.persist(orderStateMachine, String.valueOf(order.getId()));
// 已結(jié)束的流程 給key加上過(guò)期時(shí)間
if (order.getStatus() == OrderStatus.FINISH.getKey()) {
redisUtil.set(order.getId(), order.getStatus(), 60 * 60 * 24, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.error("訂單操作失敗:{}", e);
} finally{
if (orderStateMachine != null) {
// 釋放狀態(tài)機(jī)
orderStateMachine.stop();
}
}
return result;
}
private boolean autoSendEvent(OrderStatusChangeEvent changeEvent, Order order) {
boolean b = true;
while (b && changeEvent != null) {
b = sendEvent(changeEvent, order);
changeEvent = changeEvent.getNextEvent();
}
return b;
}
3.8 監(jiān)聽器
用來(lái)執(zhí)行一些不影響流程主流程的 日志、通知之類的任務(wù),可異步
@Component
@Slf4j
public class OrderStateListener extends StateMachineListenerAdapter<OrderStatus, OrderStatusChangeEvent> {
private StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine;
private Message<OrderStatusChangeEvent> message;
@Override
public void stateContext(StateContext<OrderStatus, OrderStatusChangeEvent> context) {
StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine = context.getStateMachine();
this.stateMachine = stateMachine;
// 獲取當(dāng)前事件
Message<OrderStatusChangeEvent> message = context.getMessage();
this.message = message;
// 獲取狀態(tài)機(jī) ID
String machineId = stateMachine.getId();
// 應(yīng)用場(chǎng)景:訂單狀態(tài)變更記錄
if (context.getStage() == StateContext.Stage.STATE_CHANGED) {
OrderStatus oldStatus = context.getSource().getId();
OrderStatus newStatus = context.getTarget().getId();
log.info("訂單 {} 狀態(tài)變更: {} → {}", machineId, oldStatus, newStatus);
}
if(context.getException()!=null){
log.error("狀態(tài)機(jī)異常: {}",context.getException().getMessage());
}
}
@DS("oracle-xxx")
@Override
public void stateChanged(State<OrderStatus, OrderStatusChangeEvent> from, State<OrderStatus, OrderStatusChangeEvent> to) {
// 狀態(tài)變更時(shí)觸發(fā)(如 UNPAID → PAID)
System.out.println("狀態(tài)變更: " + from.getId() + " → " + to.getId());
Order order = message.getHeaders().get("order", Order.class);
order.setStatus(to.getId().getKey());
}
@Override
public void stateEntered(State<OrderStatus, OrderStatusChangeEvent> state) {
// 進(jìn)入新狀態(tài)時(shí)觸發(fā)
System.out.println("進(jìn)入狀態(tài): " + state.getId());
}
@Override
@Async
public void eventNotAccepted(Message<OrderStatusChangeEvent> message) {
// 事件被拒絕時(shí)觸發(fā)(如非法狀態(tài)轉(zhuǎn)換)
System.err.println("事件拒絕開始: " + message.getPayload());
Order order = message.getHeaders().get("order", Order.class);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.err.println("事件拒絕結(jié)束: " + message.getPayload());
}
@Override
@Async
public void stateMachineError(StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine, Exception exception) {
// 只能建ring狀態(tài)機(jī)本身在運(yùn)行中發(fā)生的異常,不能處理action拋出的異常
// 可以在此進(jìn)行重試、告警等操作
log.error("錯(cuò)誤處理: {}", exception.getMessage());
}
@Override
public void stateExited(State<OrderStatus, OrderStatusChangeEvent> state) {
// 狀態(tài)機(jī)退出時(shí)觸發(fā)
log.info("狀態(tài)退出: {}", state.getId());
}
}3.9 持久化
這里直接用的redis持久化,可自己定義成其他持久化方式
@Configuration
@Slf4j
public class Persist<E, S> {
@Resource
private RedisConnectionFactory redisConnectionFactory;
/**
* 持久化到redis中,在分布式系統(tǒng)中使用
*
* @return
*/
@Bean(name = "stateMachineRedisPersister")
public RedisStateMachinePersister<E, S> getRedisPersister() {
RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
RepositoryStateMachinePersist p = new RepositoryStateMachinePersist<>(repository);
return new RedisStateMachinePersister<>(p);
}
}
4. 總結(jié)
1. 狀態(tài)轉(zhuǎn)移通過(guò)配置類維護(hù),可讀性高,新增狀態(tài)時(shí)維護(hù)方便。
2. 狀態(tài)之間轉(zhuǎn)移通過(guò)由配置強(qiáng)制指定,不容易出現(xiàn)邏輯錯(cuò)亂。
3. 加鎖方便,可以在統(tǒng)一的事件觸發(fā)入口加redis分布式鎖。
4. 業(yè)務(wù)邏輯處理存放在Guard?、Action?、監(jiān)聽器中,各司其職結(jié)構(gòu)清晰,代碼邏輯解耦。
到此這篇關(guān)于Spring狀態(tài)機(jī) Statemachine使用小結(jié)的文章就介紹到這了,更多相關(guān)Spring狀態(tài)機(jī) Statemachine內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java調(diào)用ChatGPT(基于SpringBoot和Vue)實(shí)現(xiàn)可連續(xù)對(duì)話和流式輸出的ChatGPT API
這篇文章主要介紹了Java調(diào)用ChatGPT(基于SpringBoot和Vue),實(shí)現(xiàn)可連續(xù)對(duì)話和流式輸出的ChatGPT API(可自定義實(shí)現(xiàn)AI助手),文中代碼示例介紹的非常詳細(xì),感興趣的朋友可以參考下2023-04-04
Java使用TCP實(shí)現(xiàn)在線聊天的示例代碼
這篇文章主要介紹了Java使用TCP實(shí)現(xiàn)在線聊天的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Java應(yīng)用程序CPU100%問(wèn)題排查優(yōu)化實(shí)戰(zhàn)
這篇文章主要介紹了如何排查和優(yōu)化Java應(yīng)用程序CPU使用率達(dá)到100%的問(wèn)題,文中通過(guò)代碼示例和圖文結(jié)合的方式講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2025-02-02
Flink自定義Sink端實(shí)現(xiàn)過(guò)程講解
這篇文章主要介紹了Flink自定義Sink端實(shí)現(xiàn)過(guò)程,在Fink官網(wǎng)中sink端只是給出了常規(guī)的write api.在我們實(shí)際開發(fā)場(chǎng)景中需要將flink處理的數(shù)據(jù)寫入kafka,hbase kudu等外部系統(tǒng)2023-01-01
BeanUtils.copyProperties()所有的空值不復(fù)制問(wèn)題
這篇文章主要介紹了BeanUtils.copyProperties()所有的空值不復(fù)制問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
MyBatis-plus使用lambda條件構(gòu)造器報(bào)錯(cuò)問(wèn)題及解決
這篇文章主要介紹了MyBatis-plus使用lambda條件構(gòu)造器報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
詳解在Java程序中運(yùn)用Redis緩存對(duì)象的方法
這篇文章主要介紹了在Java程序中運(yùn)用Redis緩存對(duì)象的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03

