状态机模式实现
一、状态机概念
1.1 定义
状态机:描述对象在不同状态下的行为以及状态之间的转移规则。
1.2 四要素
| 要素 | 说明 |
|---|---|
| 状态(State) | 对象的当前状态 |
| 事件(Event) | 触发状态转移的条件 |
| 动作(Action) | 状态转移时执行的操作 |
| 转移(Transition) | 从一个状态到另一个状态的过程 |
1.3 状态机图示
订单状态机:
┌─────────────┐ 支付成功 ┌─────────────┐ 开始制作 ┌─────────────┐
│ 待支付 │ ───────────────> │ 制作中 │ ───────────────> │ 已完成 │
│ PENDING │ │ MAKING │ │ COMPLETED │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
│ 取消订单 │ 取消订单
↓ ↓
┌─────────────┐ ┌─────────────┐
│ 已取消 │ │ 已取消 │
│ CANCELLED │ │ CANCELLED │
└─────────────┘ └─────────────┘
二、简单状态机实现
2.1 枚举方式
public enum OrderStatus {
PENDING_PAYMENT("待支付"),
MAKING("制作中"),
COMPLETED("已完成"),
CANCELLED("已取消");
private final String description;
OrderStatus(String description) {
this.description = description;
}
// 状态转移映射
private static final Map<OrderStatus, Set<OrderStatus>> ALLOWED_TRANSITIONS = Map.of(
PENDING_PAYMENT, Set.of(MAKING, CANCELLED),
MAKING, Set.of(COMPLETED, CANCELLED),
COMPLETED, Set.of(), // 终态,无转移
CANCELLED, Set.of() // 终态,无转移
);
// 验证状态转移是否合法
public boolean canTransitionTo(OrderStatus target) {
return ALLOWED_TRANSITIONS.get(this).contains(target);
}
}2.2 状态机服务
@Service
public class OrderStateMachineService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public boolean transition(Long orderId, OrderStatus targetStatus) {
Order order = orderRepository.findById(orderId).orElseThrow();
// 验证状态转移合法性
if (!order.getStatus().canTransitionTo(targetStatus)) {
throw new IllegalStateException(
String.format("无法从%s转移到%s", order.getStatus(), targetStatus)
);
}
// 使用乐观锁更新状态
int updated = orderRepository.updateStatus(
orderId,
order.getStatus(),
targetStatus
);
return updated > 0;
}
}2.3 数据库更新(乐观锁)
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Modifying
@Query("UPDATE Order o SET o.status = :newStatus, o.version = o.version + 1 " +
"WHERE o.id = :orderId AND o.status = :currentStatus AND o.version = :version")
int updateStatusWithVersion(
@Param("orderId") Long orderId,
@Param("currentStatus") OrderStatus currentStatus,
@Param("newStatus") OrderStatus newStatus,
@Param("version") int version
);
}三、使用状态机框架
3.1 Spring Statemachine
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderStatus.PENDING_PAYMENT)
.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.PENDING_PAYMENT).target(OrderStatus.MAKING)
.event(OrderEvent.PAY_SUCCESS)
.action(paySuccessAction())
.and()
.withExternal()
.source(OrderStatus.MAKING).target(OrderStatus.COMPLETED)
.event(OrderEvent.COMPLETE)
.action(completeAction())
.and()
.withExternal()
.source(OrderStatus.PENDING_PAYMENT).target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
.action(cancelAction())
.and()
.withExternal()
.source(OrderStatus.MAKING).target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
.action(cancelAction());
}
@Bean
public Action<OrderStatus, OrderEvent> paySuccessAction() {
return context -> System.out.println("支付成功,开始制作");
}
@Bean
public Action<OrderStatus, OrderEvent> completeAction() {
return context -> System.out.println("订单完成");
}
@Bean
public Action<OrderStatus, OrderEvent> cancelAction() {
return context -> System.out.println("订单已取消");
}
}3.2 事件枚举
public enum OrderEvent {
PAY_SUCCESS("支付成功"),
COMPLETE("完成"),
CANCEL("取消");
private final String description;
OrderEvent(String description) {
this.description = description;
}
}四、分布式场景下的状态机
4.1 数据库层面的并发控制
-- 状态更新必须验证当前状态
UPDATE orders
SET status = 'MAKING', version = version + 1
WHERE id = 123
AND status = 'PENDING_PAYMENT'
AND version = 1;4.2 分布式锁保护
@Service
public class DistributedStateMachineService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private OrderStateMachineService stateMachineService;
public boolean safeTransition(Long orderId, OrderStatus targetStatus) {
String lockKey = "order:state:" + orderId;
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
return stateMachineService.transition(orderId, targetStatus);
}
throw new RuntimeException("操作过于频繁,请稍后重试");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("操作被中断");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}五、状态持久化
5.1 状态机状态持久化
@Service
public class StateMachinePersistenceService {
@Autowired
private StateMachine<OrderStatus, OrderEvent> stateMachine;
@Autowired
private OrderRepository orderRepository;
public void restoreState(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
stateMachine.getStateMachineAccessor()
.doWithAllRegions(access -> {
access.resetStateMachine(new DefaultStateMachineContext<>(
order.getStatus(),
null,
null,
null
));
});
}
public void saveState(Long orderId) {
OrderStatus currentState = stateMachine.getState().getId();
orderRepository.updateStatusOnly(orderId, currentState);
}
}六、面试要点
6.1 核心概念
| 概念 | 说明 |
|---|---|
| 状态机 | 状态转移规则的集合 |
| 状态转移 | 从一个状态到另一个状态的变化 |
| 终态 | 不再转移的状态 |
| 乐观锁 | 通过版本号实现并发控制 |
6.2 常见面试问题
Q:为什么需要状态机?
A:状态机确保状态转移的合法性,防止非法状态跳转,使业务逻辑更清晰。
Q:如何防止并发状态修改?
A:使用数据库乐观锁(版本号)或分布式锁保证原子性。
Q:什么时候使用框架实现状态机?
A:状态转移规则复杂、需要持久化状态、需要监听器扩展时使用框架。