SpringBoot状态机
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
*/
@Bean
public DefaultStateMachinePersister<Object,Object,Order> persister() {
return new DefaultStateMachinePersister<>(new StateMachinePersist<>() {
@Override
public void write(StateMachineContext<Object, Object> context, Order order) throws Exception {
//todo 持久化处理
}
@Override
public 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 {
@Resource
private StateMachine<OrderStatus, OrderEvents> orderStateMachine;
@Resource
private 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;
}
@SneakyThrows
private 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 : 同步至数据仓库
如果读者对其他流程感兴趣,可以自行测试