news 2026/4/16 12:37:26

SpringBoot集成Spring Statemachine(状态机)实战教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot集成Spring Statemachine(状态机)实战教程

背景

本文将基于借款订单状态流转这个场景来实现。如果使用if-else或者switch语句来处理这些状态,代码会变得非常臃肿且难以维护。而状态机提供了一种更加结构化和可维护的方式来管理这些状态转换。

示例中涉及到:状态机的配置、数据持久化、状态恢复查询、同一事件由同一sourceStatus流转到不同targetStatus

SpringBoot集成状态机

1、首先,需要添加Spring Statemachine的依赖到Spring Boot项目的pom.xml文件中:

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

2、定义系统中订单存在的状态

public enum OrderStatusEnum { // 待审核 APPROVE_PENDING, // 审核中 APPROVE_ING, // 审核失败 APPROVE_FAILED, // 审核成功 APPROVE_SUCCESS; }

3、定义系统中触发状态变更的事件

public enum OrderEvent { // 开始审核 APPROVE_START, // 审核通过 APPROVE_SUCCESS, // 审核失败 APPROVE_FAILED; }

4、状态机-状态流转配置

@Configuration @EnableStateMachine(name = "OrderStateMachine") @Slf4j publicclass OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatusEnum, OrderEvent> { @Resource private OrderMapper orderMapper; /** * 配置状态 * * @param states * @throws Exception */ @Override public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderEvent> states) throws Exception { states.withStates() .initial(OrderStatusEnum.LOAN_PENDING) // 设置初始状态为[待审核] .states(EnumSet.allOf(OrderStatusEnum.class)); } /** * 配置状态转换事件关系 * * @param transitions * @throws Exception */ @Override public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEvent> transitions) throws Exception { transitions //当执行 【开始审核】操作时,将订单状态由待审核 -> 审核中 .withExternal().source(OrderStatusEnum.APPROVE_PENDING).target(OrderStatusEnum.APPROVE_ING).event(OrderEvent.APPROVE_START) .and() //当执行 【审核失败】操作时,将订单状态由审核中 -> 审核失败 .withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_FAILED).event(OrderEvent.APPROVE_FAILED) .and() //当执行 【审核成功】操作时,将订单状态由审核中 -> 审核成功 .withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_SUCCESS).event(OrderEvent.APPROVE_SUCCESS); } /** * 持久化配置 * * @return */ @Bean public DefaultStateMachinePersister persister() { returnnew DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEvent, BizOrder>() { @Override public void write(StateMachineContext<OrderStatusEnum, OrderEvent> context, BizOrder order) throws Exception { OrderStatusEnum orderStatus = context.getState(); log.info("订单状态持久化,订单ID:{},目标状态:{}", order.getId(), orderStatus); orderMapper.updateOrderStatus(order.getId(), orderStatus); } @Override public StateMachineContext<OrderStatusEnum, OrderEvent> read(BizOrder order) throws Exception { log.info("恢复订单状态机状态"); returnnew DefaultStateMachineContext<>(order.getStatus(), null, null, null); } }); } }

5、新建一个变更订单状态的服务

public interface BizOrderStatusService { /** * * 通用状态变更处理器 * @param incomingId * @param event */ void eventHandler(Long orderId, OrderEvent event); }
@Service publicclass BizOrderStatusServiceImpl implements BizOrderStatusService { @Resource private OrderMapper orderMapper; @Resource private StateMachine<OrderStatusEnum, OrderEvent> orderStateMachine; @Resource private StateMachinePersister<OrderStatusEnum, OrderEvent, BizOrder> persister; /** * * * @param orderId 订单id * @param event 事件类型 */ @Override public void eventHandler(Long orderId, OrderEvent event) { BizOrder order = orderMapper.getOrderById(orderId); Assert.notNull(order, "订单不存在"); // 自定义状态机参数对象(可以在此对象中定义后续需要用到的字段参数,状态配置那里如果需要做业务逻辑判断) StateMachineParam param = new StateMachineParam(); param.setBizOrder(order); Message message = MessageBuilder.withPayload(event).build(); if (!sendEvent(message, param)) { thrownew ApplicationBizException("订单状态流转异常"); } } /** * 发送订单状态转换事件 这里不要使用synchronized锁方法,效率比较低, * 分布式系统优先采用分布式锁,下单锁userId,订单状态流转锁orderId根据业务考虑使用什么。 * * @param message * @param param * @return */ private synchronized boolean sendEvent(Message<OrderEvent> message, StateMachineParam param) { boolean result = false; try { orderStateMachine.start(); //尝试恢复状态机状态 persister.restore(orderStateMachine, param.getBizOrder()); orderStateMachine.getExtendedState().getVariables().put("param", param); result = orderStateMachine.sendEvent(message); //持久化状态机状态 persister.persist(orderStateMachine, param.getBizOrder()); } catch (Exception e) { e.printStackTrace(); } finally { orderStateMachine.stop(); } return result; } }

6、调用方法执行订单状态变更,并持久化到数据库

@RestController @RequiredArgsConstructor publicclass ApproveController { privatefinal OrderStatusService orderStatusService; /** * 前端调用start方法将订单状态改为审核中,并自动持久化到数据库 * @param orderId 订单id * @return */ @PostMapping("/start") public void start(Long orderId) { orderStatusService.eventHandler(orderId,OrderEvent.APPROVE_START); } /** * 前端调用start方法将订单状态改为审核成功,并自动持久化到数据库 * @param orderId 订单id * @return */ @PostMapping("/approveSuccess") public void approveSuccess(Long orderId) { orderStatusService.eventHandler(orderId,OrderEvent.APPROVE_SUCCESS); } /** * 前端调用start方法将订单状态改为审核失败,并自动持久化到数据库 * @param orderId 订单id * @return */ @PostMapping("/approveFailed") public void approveFailed(Long orderId) { orderStatusService.eventHandler(orderId,OrderEvent.APPROVE_FAILED); } }

现在,我们已经配置了状态机并创建了服务来操作它。接下来,你可以在应用的任何部分注入OrderStatusService,并传入相应的事件来改变订单的状态。

7、总结:以上就是状态机的基础用法,一个事件对应一种来源状态(sourceStatus)和目标状态(targetStatus)。在我自己使用到的场景中还包含一个事件需要根据不同的条件将同一来源状态流转到不同的目标状态。这时我们就需要在状态映射配置中增加业务逻辑判断。

8、扩展(新增一个放款事件,该事件会将订单状态由【审核成功】流转到【放款成功】或者【部分放款成功】,具体流流转哪一个状态是由订单的放款金额决定的,如果申请金额和放款金额一致就是【放款成功】,放款金额小于申请金额就是【部分放款成功】)

8.1 我们在订单状态枚举中新增(LOAN_SUCCESS,PARTIALLY_LOAN_SUCCESS)

// 待审核 APPROVE_PENDING, // 审核中 APPROVE_ING, // 审核失败 APPROVE_FAILED, // 审核成功 APPROVE_SUCCESS, // 放款成功 LOAN_SUCCESS, // 部分放款成功 PARTIALLY_LOAN_SUCCESS;

8.2 我们在事件枚举中新增(LOAN)

// 开始审核 APPROVE_START, // 审核通过 APPROVE_SUCCESS, // 审核失败 APPROVE_FAILED, // 操作放款 LOAN;

8.3 优化一下上面的【配置状态转换事件关系】,需要在事件后面增加条件判断(通过guard()实现)

/** * 配置状态转换事件关系 * * @param transitions * @throws Exception */ @Override public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEvent> transitions) throws Exception { transitions //当执行 【开始审核】操作时,将订单状态由待审核 -> 审核中 .withExternal().source(OrderStatusEnum.APPROVE_PENDING).target(OrderStatusEnum.APPROVE_ING).event(OrderEvent.APPROVE_START) .and() //当执行 【审核失败】操作时,将订单状态由审核中 -> 审核失败 .withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_FAILED).event(OrderEvent.APPROVE_FAILED) .and() //当执行 【审核成功】操作时,将订单状态由审核中 -> 审核成功 .withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_SUCCESS).event(OrderEvent.APPROVE_SUCCESS) .and() //当执行 【放款】操作时,将订单状态由审核成功 -> 放款成功 .withExternal().source(OrderStatusEnum.APPROVE_SUCCESS).target(OrderStatusEnum.LOAN_SUCCESS).event(OrderEvent.LOAN).guard(guardForLoanSuccessByLoan()) .and() //当执行 【放款】操作时,将订单状态由审核成功 -> 部分放款成功 .withExternal().source(OrderStatusEnum.APPROVE_SUCCESS).target(OrderStatusEnum.PARTIALLY_LOAN_SUCCESS).event(OrderEvent.LOAN).guard(guardForPartiallyLoanSuccessByLoan()); } /** * 订单状态由审核通过 -> 放款成功 * 触发条件:订单申请金额=放款金额 * * @return */ @Bean public Guard<OrderStatusEnum, OrderEvent> guardForLoanSuccessByLoan() { return context -> { // 从扩展信息中获取参数 StateMachineParam param = (StateMachineParam) context.getExtendedState().getVariables().get("param"); BizOrder order = param.getBizOrder(); // 如果申请金额=放款金额 ,返回true,状态机就会流转到调用此方法的目标状态 if (order.getApplyAmount().compareTo(order.getLoanAmlunt) == 0) { returntrue; } returnfalse; }; } /** * 订单状态由审核通过 -> 部分放款成功 * 触发条件:订单申请金额<放款金额 * * @return */ @Bean public Guard<OrderStatusEnum, OrderEvent> guardForPartiallyLoanSuccessByLoan() { return context -> { // 从扩展信息中获取参数 StateMachineParam param = (StateMachineParam) context.getExtendedState().getVariables().get("param"); BizOrder order = param.getBizOrder(); // 如果申请金额<放款金额 ,返回true,状态机就会流转到调用此方法的目标状态 if (order.getApplyAmount().compareTo(order.getLoanAmlunt) < 0) { returntrue; } returnfalse; }; }

通过以上操作我们就可以实现业务中某些需要根据不同条件流转到不同状态的场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 13:42:14

程序员面试必备的Java八股文,适合所有的Java求职者!

说明 本文分享Java后端真实高频面试题&#xff0c;有详细答案&#xff0c;保你稳过面试。题目包括&#xff1a;Java基础、多线程、JVM、数据库、Redis、Shiro、Spring、SpringBoot、MyBatis、MQ、ELK、SpringCloud、设计模式等。 包含从简单到困难、从高频到低频的题目&#x…

作者头像 李华
网站建设 2026/4/16 11:09:37

揭秘Docker容器间通信难题:智能Agent互联的3种高阶解决方案

第一章&#xff1a;智能 Agent 的 Docker 容器互联在分布式系统中&#xff0c;多个智能 Agent 常以独立服务的形式运行&#xff0c;Docker 容器化技术为这些 Agent 提供了轻量级、可移植的运行环境。实现容器间的高效通信是构建协同智能系统的关键步骤。网络模式选择 Docker 支…

作者头像 李华
网站建设 2026/4/16 14:32:30

ControlNet++革命性突破:10+条件精准控制图像生成的终极指南

ControlNet革命性突破&#xff1a;10条件精准控制图像生成的终极指南 【免费下载链接】controlnet-union-sdxl-1.0 项目地址: https://ai.gitcode.com/hf_mirrors/xinsir/controlnet-union-sdxl-1.0 在AI图像生成技术飞速发展的今天&#xff0c;ControlNet作为多条件控…

作者头像 李华
网站建设 2026/4/16 12:44:10

VSCode中Qiskit代码补全失效?5步精准排查并彻底解决

第一章&#xff1a;VSCode中Qiskit代码补全失效&#xff1f;5步精准排查并彻底解决在使用 VSCode 进行 Qiskit 量子计算开发时&#xff0c;代码补全功能突然失效是常见问题&#xff0c;可能由环境配置、扩展冲突或解释器选择错误引起。通过系统性排查可快速恢复智能提示功能。确…

作者头像 李华
网站建设 2026/4/16 11:03:35

终极指南:5分钟掌握专业架构图样式定制技巧

终极指南&#xff1a;5分钟掌握专业架构图样式定制技巧 【免费下载链接】diagrams :art: Diagram as Code for prototyping cloud system architectures 项目地址: https://gitcode.com/GitHub_Trending/di/diagrams 架构图样式定制是提升技术文档专业度的关键环节。本文…

作者头像 李华