本文目录导读:

我将为您提供一个完整的Spring State Machine实战案例,以订单状态管理为例。
项目结构
spring-statemachine-demo
├── pom.xml
├── src/main/java/com/example/order/
│ ├── OrderStateMachineApplication.java
│ ├── config/
│ │ ├── OrderStateMachineConfig.java
│ │ └── StateMachineListener.java
│ ├── domain/
│ │ ├── Order.java
│ │ └── OrderEvent.java
│ ├── repository/
│ │ └── OrderRepository.java
│ ├── service/
│ │ ├── OrderService.java
│ │ └── OrderServiceImpl.java
│ └── controller/
│ └── OrderController.java
└── src/main/resources/
└── application.yml
Maven依赖 (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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-statemachine-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>11</java.version>
<spring-statemachine.version>3.2.1</spring-statemachine.version>
</properties>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring State Machine -->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>${spring-statemachine.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
主应用类
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderStateMachineApplication {
public static void main(String[] args) {
SpringApplication.run(OrderStateMachineApplication.class, args);
}
}
领域模型
Order.java
package com.example.order.domain;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_no", unique = true, length = 32)
private String orderNo;
@Column(name = "customer_name", length = 50)
private String customerName;
@Column(name = "total_amount")
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
@Column(name = "status", length = 20)
private OrderStatus status;
@Column(name = "created_time")
private LocalDateTime createdTime;
@Column(name = "updated_time")
private LocalDateTime updatedTime;
@Column(name = "description", length = 200)
private String description;
}
OrderStatus.java
package com.example.order.domain;
public enum OrderStatus {
CREATED, // 已创建
PENDING_PAYMENT, // 待支付
PAID, // 已支付
SHIPPED, // 已发货
COMPLETED, // 已完成
CANCELLED // 已取消
}
OrderEvent.java
package com.example.order.domain;
public enum OrderEvent {
SUBMIT, // 提交订单
PAY, // 支付
SHIP, // 发货
CONFIRM, // 确认收货
CANCEL // 取消订单
}
状态机配置
OrderStateMachineConfig.java
package com.example.order.config;
import com.example.order.domain.OrderEvent;
import com.example.order.domain.OrderStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import java.util.EnumSet;
@Slf4j
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineConfigurationConfigurer<OrderStatus, OrderEvent> config)
throws Exception {
config
.withConfiguration()
.machineId("orderStateMachine")
.listener(listener());
}
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states)
throws Exception {
states
.withStates()
.initial(OrderStatus.CREATED)
.states(EnumSet.allOf(OrderStatus.class))
.end(OrderStatus.COMPLETED)
.end(OrderStatus.CANCELLED);
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions)
throws Exception {
transitions
// 创建 -> 待支付
.withExternal()
.source(OrderStatus.CREATED)
.target(OrderStatus.PENDING_PAYMENT)
.event(OrderEvent.SUBMIT)
.and()
// 待支付 -> 已支付
.withExternal()
.source(OrderStatus.PENDING_PAYMENT)
.target(OrderStatus.PAID)
.event(OrderEvent.PAY)
.and()
// 待支付 -> 已取消(支付超时或用户取消)
.withExternal()
.source(OrderStatus.PENDING_PAYMENT)
.target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
.and()
// 已支付 -> 已发货
.withExternal()
.source(OrderStatus.PAID)
.target(OrderStatus.SHIPPED)
.event(OrderEvent.SHIP)
.and()
// 已发货 -> 已完成
.withExternal()
.source(OrderStatus.SHIPPED)
.target(OrderStatus.COMPLETED)
.event(OrderEvent.CONFIRM);
}
@Bean
public StateMachineListener<OrderStatus, OrderEvent> listener() {
return new StateMachineListenerAdapter<OrderStatus, OrderEvent>() {
@Override
public void stateChanged(State<OrderStatus, OrderEvent> from, State<OrderStatus, OrderEvent> to) {
log.info("状态变更: {} -> {}",
from == null ? "无" : from.getId(),
to == null ? "无" : to.getId());
}
};
}
}
仓库接口
OrderRepository.java
package com.example.order.repository;
import com.example.order.domain.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Optional<Order> findByOrderNo(String orderNo);
}
服务层
OrderService.java
package com.example.order.service;
import com.example.order.domain.Order;
import com.example.order.domain.OrderEvent;
import org.springframework.statemachine.StateMachine;
public interface OrderService {
Order createOrder(Order order);
Order submitOrder(String orderNo);
Order payOrder(String orderNo);
Order shipOrder(String orderNo);
Order confirmOrder(String orderNo);
Order cancelOrder(String orderNo);
Order getOrderByOrderNo(String orderNo);
}
OrderServiceImpl.java
package com.example.order.service;
import com.example.order.domain.Order;
import com.example.order.domain.OrderEvent;
import com.example.order.domain.OrderStatus;
import com.example.order.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineEventPublisher;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final StateMachine<OrderStatus, OrderEvent> orderStateMachine;
private final StateMachineEventPublisher eventPublisher;
@Override
@Transactional
public Order createOrder(Order order) {
order.setOrderNo(generateOrderNo());
order.setStatus(OrderStatus.CREATED);
order.setCreatedTime(LocalDateTime.now());
order.setUpdatedTime(LocalDateTime.now());
return orderRepository.save(order);
}
@Override
@Transactional
public Order submitOrder(String orderNo) {
return processOrderEvent(orderNo, OrderEvent.SUBMIT);
}
@Override
@Transactional
public Order payOrder(String orderNo) {
return processOrderEvent(orderNo, OrderEvent.PAY);
}
@Override
@Transactional
public Order shipOrder(String orderNo) {
return processOrderEvent(orderNo, OrderEvent.SHIP);
}
@Override
@Transactional
public Order confirmOrder(String orderNo) {
return processOrderEvent(orderNo, OrderEvent.CONFIRM);
}
@Override
@Transactional
public Order cancelOrder(String orderNo) {
return processOrderEvent(orderNo, OrderEvent.CANCEL);
}
@Override
public Order getOrderByOrderNo(String orderNo) {
return orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new RuntimeException("订单不存在: " + orderNo));
}
private Order processOrderEvent(String orderNo, OrderEvent event) {
Order order = getOrderByOrderNo(orderNo);
OrderStatus currentStatus = order.getStatus();
OrderStatus targetStatus = getTargetStatus(currentStatus, event);
// 检查当前状态是否允许该事件
if (!isValidTransition(currentStatus, event)) {
throw new RuntimeException(
String.format("订单状态不允许该操作: 当前状态=%s, 事件=%s",
currentStatus, event));
}
log.info("订单[{}]执行事件: {},状态: {} -> {}",
orderNo, event, currentStatus, targetStatus);
try {
// 发送事件到状态机
orderStateMachine.sendEvent(MessageBuilder
.withPayload(event)
.setHeader("orderNo", orderNo)
.build());
// 更新订单状态
order.setStatus(targetStatus);
order.setUpdatedTime(LocalDateTime.now());
// 根据事件设置描述信息
order.setDescription(event.getDescription());
return orderRepository.save(order);
} catch (Exception e) {
log.error("状态机处理失败: {}", e.getMessage());
throw new RuntimeException("状态机处理失败: " + e.getMessage());
}
}
private OrderStatus getTargetStatus(OrderStatus current, OrderEvent event) {
switch (current) {
case CREATED:
if (event == OrderEvent.SUBMIT) return OrderStatus.PENDING_PAYMENT;
break;
case PENDING_PAYMENT:
if (event == OrderEvent.PAY) return OrderStatus.PAID;
if (event == OrderEvent.CANCEL) return OrderStatus.CANCELLED;
break;
case PAID:
if (event == OrderEvent.SHIP) return OrderStatus.SHIPPED;
break;
case SHIPPED:
if (event == OrderEvent.CONFIRM) return OrderStatus.COMPLETED;
break;
default:
break;
}
throw new RuntimeException("无效的状态转换");
}
private boolean isValidTransition(OrderStatus current, OrderEvent event) {
try {
getTargetStatus(current, event);
return true;
} catch (RuntimeException e) {
return false;
}
}
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis() +
UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
}
控制器
OrderController.java
package com.example.order.controller;
import com.example.order.domain.Order;
import com.example.order.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
return ResponseEntity.ok(orderService.createOrder(order));
}
@GetMapping("/{orderNo}")
public ResponseEntity<Order> getOrder(@PathVariable String orderNo) {
return ResponseEntity.ok(orderService.getOrderByOrderNo(orderNo));
}
@PostMapping("/{orderNo}/submit")
public ResponseEntity<Order> submitOrder(@PathVariable String orderNo) {
return ResponseEntity.ok(orderService.submitOrder(orderNo));
}
@PostMapping("/{orderNo}/pay")
public ResponseEntity<Order> payOrder(@PathVariable String orderNo) {
return ResponseEntity.ok(orderService.payOrder(orderNo));
}
@PostMapping("/{orderNo}/ship")
public ResponseEntity<Order> shipOrder(@PathVariable String orderNo) {
return ResponseEntity.ok(orderService.shipOrder(orderNo));
}
@PostMapping("/{orderNo}/confirm")
public ResponseEntity<Order> confirmOrder(@PathVariable String orderNo) {
return ResponseEntity.ok(orderService.confirmOrder(orderNo));
}
@PostMapping("/{orderNo}/cancel")
public ResponseEntity<Order> cancelOrder(@PathVariable String orderNo) {
return ResponseEntity.ok(orderService.cancelOrder(orderNo));
}
}
配置文件
application.yml
server:
port: 8080
spring:
application:
name: order-state-machine-demo
datasource:
url: jdbc:h2:mem:orderdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
show-sql: true
h2:
console:
enabled: true
path: /h2-console
logging:
level:
com.example.order: DEBUG
org.springframework.statemachine: INFO
测试用例
OrderServiceTest.java
package com.example.order.service;
import com.example.order.domain.Order;
import com.example.order.domain.OrderStatus;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testOrderLifecycle() {
// 1. 创建订单
Order order = new Order();
order.setCustomerName("张三");
order.setTotalAmount(new BigDecimal("299.00"));
Order createdOrder = orderService.createOrder(order);
assertEquals(OrderStatus.CREATED, createdOrder.getStatus());
assertNotNull(createdOrder.getOrderNo());
String orderNo = createdOrder.getOrderNo();
// 2. 提交订单
Order submittedOrder = orderService.submitOrder(orderNo);
assertEquals(OrderStatus.PENDING_PAYMENT, submittedOrder.getStatus());
// 3. 支付订单
Order paidOrder = orderService.payOrder(orderNo);
assertEquals(OrderStatus.PAID, paidOrder.getStatus());
// 4. 发货
Order shippedOrder = orderService.shipOrder(orderNo);
assertEquals(OrderStatus.SHIPPED, shippedOrder.getStatus());
// 5. 确认收货
Order completedOrder = orderService.confirmOrder(orderNo);
assertEquals(OrderStatus.COMPLETED, completedOrder.getStatus());
}
@Test
public void testInvalidTransition() {
// 创建订单
Order order = new Order();
order.setCustomerName("李四");
order.setTotalAmount(new BigDecimal("199.00"));
Order createdOrder = orderService.createOrder(order);
String orderNo = createdOrder.getOrderNo();
// 尝试从未支付状态直接发货(应该失败)
assertThrows(RuntimeException.class, () -> {
orderService.shipOrder(orderNo);
});
}
@Test
public void testCancelOrder() {
// 创建订单
Order order = new Order();
order.setCustomerName("王五");
order.setTotalAmount(new BigDecimal("399.00"));
Order createdOrder = orderService.createOrder(order);
String orderNo = createdOrder.getOrderNo();
// 提交订单
orderService.submitOrder(orderNo);
// 取消订单
Order cancelledOrder = orderService.cancelOrder(orderNo);
assertEquals(OrderStatus.CANCELLED, cancelledOrder.getStatus());
}
}
使用示例
正常流程测试
# 1. 创建订单
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
"customerName": "张三",
"totalAmount": 299.00
}'
# 2. 提交订单
curl -X POST http://localhost:8080/api/orders/{orderNo}/submit
# 3. 支付订单
curl -X POST http://localhost:8080/api/orders/{orderNo}/pay
# 4. 发货
curl -X POST http://localhost:8080/api/orders/{orderNo}/ship
# 5. 确认收货
curl -X POST http://localhost:8080/api/orders/{orderNo}/confirm
核心特性说明
状态定义清晰
- 使用枚举定义所有订单状态
- 每个状态都有明确的含义
事件驱动
- 通过事件触发状态转换
- 支持异步事件处理
完整的状态转换控制
- 定义合法的转换路径
- 自动阻止非法操作
监听器支持
- 监听状态变化
- 可扩展业务逻辑
持久化
- 状态自动持久化到数据库
- 支持复杂业务场景
这个案例展示了Spring State Machine的核心用法,包括状态定义、事件触发、转换控制、监听器等,您可以根据实际业务需求进行扩展和修改。