状态模式:工作流引擎的状态机设计与Spring StateMachine实战
状态模式:工作流引擎的状态机设计与Spring StateMachine实战
适读人群:中高级Java开发者 | 阅读时长:约25分钟 | 模式类型:行为型
开篇故事
做过电商的朋友都知道,订单状态流转是最复杂的业务逻辑之一。订单可能从"待支付"变为"已支付",也可能变为"已取消";"已支付"可以变为"备货中",也可能在某些情况下触发退款变为"已退款"……
最让人头疼的是,随着业务迭代,状态越来越多,每次改状态流转逻辑,都要在一大堆 if-else 里找到正确的位置。有一次我们加了一个新状态"待评价",改了20多处代码,还是漏改了一处,导致某种情况下订单永远停留在"待评价"状态无法流转。
那次之后,我们用状态模式重构了订单状态机。每个状态的行为封装在独立的状态类中,状态转换规则清晰可配置,新增状态只需新增一个状态类,不需要修改已有代码。
这正是状态模式的核心价值:将每种状态的行为封装在独立的类中,使状态切换变成对象替换,而不是 if-else 堆砌。
一、模式动机:消除状态切换的 if-else
状态模式(State Pattern):允许对象在内部状态改变时改变它的行为,看起来好像修改了它的类。
核心:把"状态相关的行为"从主类中分离出去,封装在独立的状态类中。状态切换时,主类(Context)持有的状态对象发生改变,行为也随之改变。
适用场景:
- 对象的行为依赖于它的状态,且状态在运行时频繁改变
- 状态判断逻辑复杂,有大量 if-else/switch-case 且随业务持续膨胀
- 工作流、审批流、订单状态机等业务场景
二、模式结构
三、订单状态机的完整实现
3.1 状态枚举与事件枚举
/**
* 订单状态枚举
*/
public enum OrderState {
PENDING_PAYMENT("待支付"),
PAID("已支付"),
PREPARING("备货中"),
SHIPPED("已发货"),
DELIVERED("已收货"),
COMPLETED("已完成"),
CANCELLED("已取消"),
REFUNDING("退款中"),
REFUNDED("已退款"),
PENDING_REVIEW("待评价");
private final String description;
OrderState(String description) {
this.description = description;
}
public String getDescription() { return description; }
}
/**
* 订单事件枚举
*/
public enum OrderEvent {
PAY("支付"),
CANCEL("取消"),
START_PREPARING("开始备货"),
SHIP("发货"),
CONFIRM_DELIVERY("确认收货"),
COMPLETE("完成"),
REQUEST_REFUND("申请退款"),
COMPLETE_REFUND("退款完成"),
REJECT_REFUND("拒绝退款"),
SUBMIT_REVIEW("提交评价");
private final String description;
OrderEvent(String description) {
this.description = description;
}
}3.2 状态接口与各状态实现
/**
* 订单状态接口(State)
*/
public interface OrderStateHandler {
/**
* 处理状态相关的业务操作
*/
void onEnter(OrderStateMachine stateMachine, Order order);
void onExit(OrderStateMachine stateMachine, Order order);
/**
* 检查是否可以发生指定事件
*/
boolean canHandle(OrderEvent event);
/**
* 处理事件并转换状态(返回新状态)
*/
OrderState handle(OrderEvent event, Order order, OrderStateMachine stateMachine);
/**
* 当前状态
*/
OrderState getState();
}
/**
* 抽象状态基类(提供默认实现)
*/
@Slf4j
public abstract class AbstractOrderStateHandler implements OrderStateHandler {
@Override
public void onEnter(OrderStateMachine stateMachine, Order order) {
log.info("Order {} entering state: {}", order.getId(), getState().getDescription());
}
@Override
public void onExit(OrderStateMachine stateMachine, Order order) {
log.debug("Order {} exiting state: {}", order.getId(), getState().getDescription());
}
@Override
public OrderState handle(OrderEvent event, Order order, OrderStateMachine stateMachine) {
if (!canHandle(event)) {
throw new IllegalStateException(String.format(
"Order %s in state %s cannot handle event %s",
order.getId(), getState(), event
));
}
return doHandle(event, order, stateMachine);
}
protected abstract OrderState doHandle(OrderEvent event, Order order, OrderStateMachine stateMachine);
}
/**
* 待支付状态(ConcreteState)
*/
@Component
@Slf4j
public class PendingPaymentStateHandler extends AbstractOrderStateHandler {
@Autowired
private PaymentService paymentService;
@Override
public OrderState getState() { return OrderState.PENDING_PAYMENT; }
@Override
public boolean canHandle(OrderEvent event) {
return event == OrderEvent.PAY || event == OrderEvent.CANCEL;
}
@Override
public void onEnter(OrderStateMachine stateMachine, Order order) {
super.onEnter(stateMachine, order);
// 进入待支付状态:启动支付超时定时器
stateMachine.schedulePaymentTimeout(order.getId(), 30); // 30分钟内支付
}
@Override
protected OrderState doHandle(OrderEvent event, Order order, OrderStateMachine stateMachine) {
return switch (event) {
case PAY -> {
// 验证支付状态
if (!paymentService.verifyPayment(order.getPaymentOrderId())) {
throw new IllegalStateException("Payment verification failed");
}
order.setPaidAt(LocalDateTime.now());
stateMachine.cancelPaymentTimeout(order.getId()); // 取消超时定时器
yield OrderState.PAID;
}
case CANCEL -> {
order.setCancelledAt(LocalDateTime.now());
order.setCancelReason("用户主动取消");
yield OrderState.CANCELLED;
}
default -> throw new IllegalArgumentException("Unexpected event: " + event);
};
}
}
/**
* 已发货状态(ConcreteState)
*/
@Component
@Slf4j
public class ShippedStateHandler extends AbstractOrderStateHandler {
@Autowired
private LogisticsService logisticsService;
@Override
public OrderState getState() { return OrderState.SHIPPED; }
@Override
public boolean canHandle(OrderEvent event) {
return event == OrderEvent.CONFIRM_DELIVERY || event == OrderEvent.REQUEST_REFUND;
}
@Override
public void onEnter(OrderStateMachine stateMachine, Order order) {
super.onEnter(stateMachine, order);
// 已发货:启动自动确认收货定时器(15天后自动确认)
stateMachine.scheduleAutoConfirmDelivery(order.getId(), 15);
}
@Override
protected OrderState doHandle(OrderEvent event, Order order, OrderStateMachine stateMachine) {
return switch (event) {
case CONFIRM_DELIVERY -> {
order.setDeliveredAt(LocalDateTime.now());
stateMachine.cancelAutoConfirmDelivery(order.getId());
yield OrderState.DELIVERED;
}
case REQUEST_REFUND -> {
// 物流途中申请退款,触发退货流程
logisticsService.initiateReturn(order.getTrackingNumber());
order.setRefundRequestedAt(LocalDateTime.now());
yield OrderState.REFUNDING;
}
default -> throw new IllegalArgumentException("Unexpected event: " + event);
};
}
}3.3 状态机核心实现
/**
* 订单状态机(Context)
*/
@Service
@Slf4j
public class OrderStateMachine {
private final Map<OrderState, OrderStateHandler> stateHandlers;
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderStateTransitionRepository transitionRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private ScheduledTaskService scheduledTaskService;
public OrderStateMachine(List<OrderStateHandler> handlers) {
stateHandlers = handlers.stream()
.collect(Collectors.toMap(OrderStateHandler::getState, Function.identity()));
log.info("OrderStateMachine initialized with {} state handlers: {}",
stateHandlers.size(), stateHandlers.keySet());
}
/**
* 触发状态事件(核心方法)
*/
@Transactional
public Order triggerEvent(String orderId, OrderEvent event, Map<String, Object> params) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
OrderState currentState = order.getStatus();
OrderStateHandler handler = stateHandlers.get(currentState);
if (handler == null) {
throw new IllegalStateException("No handler for state: " + currentState);
}
if (!handler.canHandle(event)) {
throw new IllegalStateException(String.format(
"Order %s in state %s cannot handle event %s",
orderId, currentState, event
));
}
log.info("Order {} state transition: {} --[{}]--> ?", orderId, currentState, event);
// 执行状态退出逻辑
handler.onExit(this, order);
// 执行状态转换逻辑
OrderState newState = handler.handle(event, order, this);
// 更新状态
OrderState previousState = order.getStatus();
order.setStatus(newState);
orderRepository.save(order);
// 记录状态转换历史
recordTransition(orderId, previousState, event, newState, params);
// 执行新状态进入逻辑
OrderStateHandler newHandler = stateHandlers.get(newState);
if (newHandler != null) {
newHandler.onEnter(this, order);
}
// 发布状态变更事件
eventPublisher.publishEvent(new OrderStateChangedEvent(this, orderId, previousState, newState, event));
log.info("Order {} state transition completed: {} --[{}]--> {}",
orderId, previousState, event, newState);
return order;
}
/**
* 检查当前状态是否可以发生某个事件
*/
public boolean canTrigger(String orderId, OrderEvent event) {
Order order = orderRepository.findById(orderId).orElse(null);
if (order == null) return false;
OrderStateHandler handler = stateHandlers.get(order.getStatus());
return handler != null && handler.canHandle(event);
}
private void recordTransition(String orderId, OrderState from, OrderEvent event,
OrderState to, Map<String, Object> params) {
OrderStateTransition transition = OrderStateTransition.builder()
.orderId(orderId)
.fromState(from)
.event(event)
.toState(to)
.transitionTime(LocalDateTime.now())
.params(params)
.build();
transitionRepository.save(transition);
}
public void schedulePaymentTimeout(String orderId, int minutes) {
scheduledTaskService.scheduleOnce(
"PAYMENT_TIMEOUT_" + orderId,
Duration.ofMinutes(minutes),
() -> {
if (canTrigger(orderId, OrderEvent.CANCEL)) {
Map<String, Object> params = Map.of("reason", "支付超时自动取消");
triggerEvent(orderId, OrderEvent.CANCEL, params);
}
}
);
}
public void cancelPaymentTimeout(String orderId) {
scheduledTaskService.cancelTask("PAYMENT_TIMEOUT_" + orderId);
}
public void scheduleAutoConfirmDelivery(String orderId, int days) {
scheduledTaskService.scheduleOnce(
"AUTO_CONFIRM_DELIVERY_" + orderId,
Duration.ofDays(days),
() -> {
if (canTrigger(orderId, OrderEvent.CONFIRM_DELIVERY)) {
Map<String, Object> params = Map.of("reason", "超时自动确认收货");
triggerEvent(orderId, OrderEvent.CONFIRM_DELIVERY, params);
}
}
);
}
public void cancelAutoConfirmDelivery(String orderId) {
scheduledTaskService.cancelTask("AUTO_CONFIRM_DELIVERY_" + orderId);
}
}3.4 Spring StateMachine 配置(Spring 官方状态机框架)
/**
* 使用 Spring StateMachine 框架的配置(替代手写状态机)
*/
@Configuration
@EnableStateMachineFactory
@Slf4j
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderState.PENDING_PAYMENT)
.states(EnumSet.allOf(OrderState.class))
.end(OrderState.COMPLETED)
.end(OrderState.CANCELLED)
.end(OrderState.REFUNDED);
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
// 待支付 -> 已支付
.withExternal()
.source(OrderState.PENDING_PAYMENT).target(OrderState.PAID)
.event(OrderEvent.PAY)
.action(payAction()) // 执行支付成功后的业务逻辑
.guard(payGuard()) // 检查支付条件
.and()
// 待支付 -> 已取消
.withExternal()
.source(OrderState.PENDING_PAYMENT).target(OrderState.CANCELLED)
.event(OrderEvent.CANCEL)
.and()
// 已支付 -> 备货中
.withExternal()
.source(OrderState.PAID).target(OrderState.PREPARING)
.event(OrderEvent.START_PREPARING)
.and()
// 备货中 -> 已发货
.withExternal()
.source(OrderState.PREPARING).target(OrderState.SHIPPED)
.event(OrderEvent.SHIP)
.action(shipAction())
.and()
// 已发货 -> 已收货
.withExternal()
.source(OrderState.SHIPPED).target(OrderState.DELIVERED)
.event(OrderEvent.CONFIRM_DELIVERY)
.and()
// 已收货 -> 待评价
.withExternal()
.source(OrderState.DELIVERED).target(OrderState.PENDING_REVIEW)
.event(OrderEvent.COMPLETE)
.and()
// 待评价 -> 已完成
.withExternal()
.source(OrderState.PENDING_REVIEW).target(OrderState.COMPLETED)
.event(OrderEvent.SUBMIT_REVIEW)
.and()
// 退款相关
.withExternal()
.source(OrderState.SHIPPED).target(OrderState.REFUNDING)
.event(OrderEvent.REQUEST_REFUND)
.and()
.withExternal()
.source(OrderState.REFUNDING).target(OrderState.REFUNDED)
.event(OrderEvent.COMPLETE_REFUND)
.and()
.withExternal()
.source(OrderState.REFUNDING).target(OrderState.SHIPPED)
.event(OrderEvent.REJECT_REFUND);
}
@Bean
public Action<OrderState, OrderEvent> payAction() {
return context -> {
String orderId = (String) context.getExtendedState().getVariables().get("orderId");
log.info("Pay action executed for order: {}", orderId);
// 支付成功后的业务逻辑
};
}
@Bean
public Guard<OrderState, OrderEvent> payGuard() {
return context -> {
// 检查是否满足支付条件(比如订单未过期)
return true;
};
}
@Bean
public Action<OrderState, OrderEvent> shipAction() {
return context -> {
String orderId = (String) context.getExtendedState().getVariables().get("orderId");
log.info("Ship action executed for order: {}", orderId);
};
}
}五、踩坑实录
坑一:状态机并发问题
多个线程同时触发同一个订单的状态事件(比如用户在两个设备上同时点击"支付"),会导致状态竞争,最终可能出现不合法的状态。
解决方案:在 triggerEvent 方法上加分布式锁(以 orderId 为 key),确保同一个订单的状态变更是串行的:
@Around("@annotation(OrderStateLock)")
public Object lockOrderStateChange(ProceedingJoinPoint joinPoint) throws Throwable {
String orderId = extractOrderId(joinPoint);
RLock lock = redissonClient.getLock("order:state:" + orderId);
if (!lock.tryLock(3, 10, TimeUnit.SECONDS)) {
throw new ConcurrentStateChangeException("订单状态变更中,请稍后重试");
}
try {
return joinPoint.proceed();
} finally {
lock.unlock();
}
}坑二:状态机的持久化
Spring StateMachine 默认状态机是内存的,应用重启后状态丢失。在分布式系统中,状态必须持久化到数据库,每次触发事件时先从数据库恢复状态机,处理完后保存状态。
Spring StateMachine 提供了 StateMachinePersist 接口用于状态持久化,需要结合 Redis 或数据库实现。
坑三:状态转换的副作用需要幂等
onEnter 中启动了支付超时定时器,如果因为网络问题事务回滚后重试,会启动两个定时器。必须使用幂等的方式设置定时器(先取消同名任务,再设置新的)。
六、总结
状态模式是处理复杂状态转换逻辑的最佳方案。在电商、工作流、审批流等业务场景中,它的价值体现在:
- 每个状态的逻辑独立封装,不会互相干扰,新增状态不影响已有状态。
- 状态转换规则集中管理,可以从配置中清晰看到状态机的全貌。
- 行为随状态自动切换,不需要每次都判断当前状态。
Spring StateMachine 框架在企业级应用中非常适用,但对于简单的状态机,手写反而更直观可控。选型建议:状态数 < 10 且状态机简单时,手写;状态数较多、有复杂的 guard/action/history 需求时,用 Spring StateMachine。
