状态机转换失败时应抛出携带上下文的InvalidStateMethodException,由状态机核心逻辑自动校验并抛出,禁止业务方法手动判断后throw;需继承RuntimeException并提供businessId、currentState、event等字段,全局异常处理器应捕获该异常返回400及结构化错误响应。

在Java领域模型中,状态机转换失败时抛出 InvalidStateMethodException 是一种常见且合理的防御性设计,但关键在于:这个异常应明确反映“非法状态迁移”,而非笼统的业务逻辑错误;它应由状态机核心逻辑主动抛出,而非在业务方法里随意 throw。
状态机应封装迁移校验逻辑
理想状态下,状态变更不应散落在各 service 方法中,而应集中于状态机(如使用 spring-statemachine、squirrel-fsm 或自研轻量 FSM)。状态机在 transition() 或 fire(event) 时,自动校验当前状态 + 触发事件是否允许跳转。若不合法,直接抛出 InvalidStateMethodException,并附带清晰上下文:
- 当前状态(e.g.
DRAFT) - 目标事件(e.g.
APPROVE) - 预期允许的源状态列表(e.g.
[SUBMITTED, REJECTED])
自定义异常需携带必要诊断信息
InvalidStateMethodException 不应是空壳 RuntimeException。建议继承 RuntimeException,并提供构造函数支持传入状态、事件、业务ID等:
public class InvalidStateMethodException extends RuntimeException {
private final String businessId;
private final String currentState;
private final String event;
public InvalidStateMethodException(String businessId, String currentState, String event) {
super(String.format("Invalid state transition: [%s] cannot handle event [%s] in state [%s]",
businessId, event, currentState));
this.businessId = businessId;
this.currentState = currentState;
this.event = event;
}
// getter...
}
避免在业务方法中手动校验后 throw
不要这样写:
立即学习“Java免费学习笔记(深入)”;
if (!order.canApprove()) { // 手动判断
throw new InvalidStateMethodException(...);
}
order.approve(); // 真正变更状态
这会导致校验逻辑与状态变更分离,易出现竞态或遗漏。正确做法是让 order.approve() 内部调用状态机的 fire(ApproveEvent),由状态机统一拦截非法迁移并抛出异常。
全局异常处理需区分对待
在 Spring MVC 或 WebFlux 中,应配置专门的 @ExceptionHandler 捕获该异常,并返回 400 Bad Request 及结构化错误响应,例如:
- HTTP 状态码:400
- 错误码:
INVALID_STATE_TRANSITION - 提示语:
订单当前状态不允许执行审批操作 - 可选字段:
current_state,allowed_events


















