Spring Boot 状态机(State Machine)是 Spring Framework 提供的一种用于实现复杂业务逻辑的状态管理工具。它基于有限状态机(Finite State Machine, FSM)的概念,允许开发者定义一组状态、事件以及它们之间的转换规则。这在处理具有多个步骤或条件的工作流时特别有用。
Spring StateMachine 组件
Spring StateMachine 是一个专门设计来帮助构建和管理状态机的库。它提供了丰富的功能来简化状态机的配置和使用。以下是几个关键概念:
- 状态(States):表示系统可以处于的不同状况。
- 事件(Events):触发从一个状态到另一个状态的转换。
- 转换(Transitions):定义了在特定事件发生时如何从一个状态转移到另一个状态。
- 动作(Actions):当进入某个状态或执行某些转换时可以执行的操作。
- 守卫(Guards):条件检查器,用于确定是否允许特定转换发生。
如下图示例:有限的状态集是“opend”以及“closed”。如果“现态”是“opend”,当“条件”为“Close”时,执行的“动作”是“close door”,次态则为“closed”。状态机逻辑执行完毕后“closed”则变成了“现态”。
应用场景
Spring 状态机(Spring State Machine)作为一种强大的状态管理和转换工具,在多个领域有着广泛的应用场景。以下是几个典型示例的详细解释:
1. 订单生命周期管理
在电商应用中,订单状态可能经历创建、支付、发货、确认收货直至完成或取消等多个状态变化。通过 Spring State Machine,你可以清晰地定义并控制这些状态之间的合法转换过程。例如:
- 创建订单:当用户提交订单时,状态机从初始状态
CREATED
开始。 - 支付订单:一旦支付成功,状态机接收事件
PAYMENT_RECEIVED
,从而将订单状态转移到PAID
。 - 发货:仓库系统接收到发货指令后,状态机会根据事件
SHIP_ORDER
将订单状态转移到SHIPPED
。 - 确认收货:买家确认收货后,状态机接收
DELIVERED
事件,订单进入COMPLETED
状态。 - 取消订单:如果在任意步骤出现问题,可以通过
CANCEL_ORDER
事件将订单状态转移到CANCELED
。
同时,在每个状态变迁时,可以触发相应的操作,如发送邮件通知、更新库存等。
2. 工作流引擎
在企业级应用中,诸如请假审批流程、报销流程等工作流通常具有多个步骤和决策点。状态机可以用来描述每个步骤之间的关系及转换条件,确保流程按照预设规则进行。例如:
- 发起申请:员工提交请假申请,状态机从
DRAFT
转移到SUBMITTED
。 - 主管审批:主管批准或拒绝申请,状态机根据结果转移到
APPROVED
或REJECTED
。 - HR 复核:对于特定类型的请假,可能需要 HR 进行复核,状态机进一步转移到
HR_REVIEW
。 - 最终确定:所有审批完成后,状态机最终转移到
CONFIRMED
或直接结束流程。
3. 游戏逻辑
在游戏开发中,游戏角色、游戏关卡、战斗场景等都有各自的状态。状态机可用于实现角色的不同行动模式切换、关卡过关条件判断、战斗状态循环等复杂逻辑。例如:
- 角色状态:玩家角色可以在
IDLE
、MOVING
、ATTACKING
、DEFENDING
等状态之间切换,每种状态对应不同的行为模式。 - 关卡状态:关卡可以从
START
到PLAYING
再到COMPLETED
或FAILED
,并且可以根据玩家的表现动态调整难度。 - 战斗状态:战斗场景中的状态机可以管理回合制战斗中的
PLAYER_TURN
和ENEMY_TURN
,以及处理VICTORY
或DEFEAT
结果。
4. 设备状态监控
在物联网(IoT)应用中,对设备运行状态进行实时跟踪和管理时,可以根据设备接收到的各种信号或指令触发状态转变。例如:
- 开机/待机/运行:设备根据电源开关、用户交互或其他传感器数据在
POWER_ON
、STANDBY
和RUNNING
状态间切换。 - 故障检测:设备检测到异常情况时,状态机会转移到
ERROR
状态,并触发报警机制。 - 维修状态:设备进入维修模式后,状态机保持在
MAINTENANCE
状态直到修复完成。
订单流程扭转简单示例
pom.xml 添加依赖
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-starter</artifactId><version>4.0.0</version></dependency>
订单模型
这里方便演示,其他的字段根据需要自行添加
package com.coderlk.state.model;import com.coderlk.state.config.OrderStatus;
import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class Order {private OrderStatus status;}
订单状态
package com.coderlk.state.config;/*** 订单状态*/
public enum OrderStatus {//待支付,待发货,待收货,订单结束INIT , PAYED, WAIT_DELIVERY, RECEIVED;
}
订单状态改变事件
package com.coderlk.state.config;/*** 订单状态改变事件*/
public enum OrderEvents {//支付,发货,确认收货PAY, DELIVERY, RECEIVE;
}
配置状态机
package com.coderlk.state.config;import com.coderlk.state.model.Order;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
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 org.springframework.statemachine.persist.DefaultStateMachinePersister;
import org.springframework.statemachine.support.DefaultStateMachineContext;import java.util.EnumSet;/*** 订单状态机配置*/
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvents> {/*** 配置状态** @param states* @throws Exception*/public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvents> states) throws Exception {states.withStates().initial(OrderStatus.INIT).states(EnumSet.allOf(OrderStatus.class));}/*** 配置状态转换事件关系** @param transitions* @throws Exception*/public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvents> transitions) throws Exception {transitions.withExternal().source(OrderStatus.INIT).target(OrderStatus.PAYED).event(OrderEvents.PAY).and().withExternal().source(OrderStatus.PAYED).target(OrderStatus.WAIT_DELIVERY).event(OrderEvents.DELIVERY).and().withExternal().source(OrderStatus.WAIT_DELIVERY).target(OrderStatus.RECEIVED).event(OrderEvents.RECEIVE);}/*** 持久化配置* 在实际使用中,可以配合Redis等进行持久化操作** @return*/@Beanpublic DefaultStateMachinePersister<Object,Object,Order> persister() {return new DefaultStateMachinePersister<>(new StateMachinePersist<>() {@Overridepublic void write(StateMachineContext<Object, Object> context, Order order) throws Exception {//todo 持久化处理}@Overridepublic StateMachineContext<Object, Object> read(Order order) throws Exception {//此处直接获取Order中的状态,其实并没有进行持久化读取操作return new DefaultStateMachineContext<>(order.getStatus(), null, null, null);}});}
}
处理事件
package com.coderlk.state.service;import com.coderlk.state.config.OrderStatus;
import com.coderlk.state.config.OrderEvents;
import com.coderlk.state.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;@Component("orderStateListener")
@Slf4j
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListener {@OnTransition( source = "INIT" ,target = "PAYED")public boolean pay(Message<OrderEvents> message) {Order order = (Order) message.getHeaders().get("order");order.setStatus(OrderStatus.PAYED);log.info("订单表保存数据");log.info("发送站内消息");log.info("回调支付接口");log.info("同步至数据仓库");return true;}@OnTransition( source = "PAYED" , target = "WAIT_DELIVERY")public boolean delivery(Message<OrderEvents> message) {Order order = (Order) message.getHeaders().get("order");order.setStatus(OrderStatus.WAIT_DELIVERY);log.info("创建物流订单");log.info("更新物流订单状态");log.info("同步至数据仓库");return true;}@OnTransition(source = "SHIPPED" , target = "RECEIVED")public boolean receive(Message<OrderEvents> message){Order order = (Order) message.getHeaders().get("order");order.setStatus(OrderStatus.PAYED);log.info("更新订单数据");log.info("同步至数据仓库");return true;}
}
package com.coderlk.state.service;import com.coderlk.state.config.OrderStatus;
import com.coderlk.state.config.OrderEvents;
import com.coderlk.state.model.Order;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
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;@Component("orderProcessor")
@Slf4j
public class OrderProcessor {@Resourceprivate StateMachine<OrderStatus, OrderEvents> orderStateMachine;@Resourceprivate StateMachinePersister<OrderStatus, OrderEvents, Order> persister;public Boolean process(Order order, OrderEvents event){Message<OrderEvents> message = MessageBuilder.withPayload(event).setHeader("order", order).build();boolean b = sendEvent(message);return b;}@SneakyThrowsprivate boolean sendEvent(Message<OrderEvents> message) {Order order = (Order) message.getHeaders().get("order");persister.restore(orderStateMachine, order);boolean result = orderStateMachine.sendEvent(message);return result;}
}
启动
package com.coderlk.state;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class StateMachineApplication {public static void main(String[] args) {SpringApplication.run(StateMachineApplication.class, args);}}
测试
支付流程
2024-12-24T15:26:19.088+08:00 INFO 44471 --- [ main] c.c.state.service.OrderStateListener : 订单表保存数据
2024-12-24T15:26:19.088+08:00 INFO 44471 --- [ main] c.c.state.service.OrderStateListener : 发送站内消息
2024-12-24T15:26:19.088+08:00 INFO 44471 --- [ main] c.c.state.service.OrderStateListener : 回调支付接口
2024-12-24T15:26:19.089+08:00 INFO 44471 --- [ main] c.c.state.service.OrderStateListener : 同步至数据仓库
如果读者对其他流程感兴趣,可以自行测试