java中自定义异常配合状态机处理业务异常的核心是让异常明确表达状态流转意图,由状态机内核主动抛出invalidstatetransitionexception等 unchecked 异常,携带businessid、currentstate、event等上下文,统一拦截返回400响应。

Java 中自定义异常配合状态机处理业务异常,核心是让异常成为状态流转意图的明确表达,而不是兜底补救的“事后烟雾弹”。关键不在于能不能 throw,而在于谁来 throw、什么时候 throw、带什么信息 throw。
异常必须由状态机内核主动抛出
状态变更逻辑应完全收口在状态机中(如 Squirrel-FSM、Spring StateMachine 或自研 FSM),禁止在 service 层手动判断状态再调用变更方法。否则校验与执行分离,易出现竞态或遗漏。
- 正确做法:业务方法直接调用
stateMachine.fire(event),由状态机内部自动校验当前状态 + 事件是否合法 - 非法迁移发生时,状态机立即中断 transition,并抛出
InvalidStateTransitionException或InvalidStateMethodException - 避免写类似
if (!order.canApprove()) throw ...; order.approve();这类割裂逻辑
自定义异常要携带可诊断的上下文字段
异常不是日志,但必须支撑快速定位。它应包含业务身份和迁移上下文,而非堆砌业务数据。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 必含字段:
businessId(如订单号)、currentState(如 DRAFT)、event(如 APPROVE) - 推荐提供构造函数支持组合传参,例如:
new InvalidStateTransitionException("ORD-1001", "DRAFT", "APPROVE") - 消息模板建议结构化,如
"业务[%s]在状态[%s]下不支持事件[%s]",便于日志提取和监控告警 - 不要在异常类里加
userId、orderId等实体字段——这些应通过日志记录,而非塞进异常对象
全局统一拦截并转为标准响应
这类异常属于明确的客户端错误(400 Bad Request),不应被吞掉或转成 500。
- 在 Spring Boot 中用
@ControllerAdvice捕获InvalidStateTransitionException - 返回 HTTP 400 + 结构化 JSON,例如:
{"code": 400, "message": "当前状态不允许执行审批操作", "detail": {"businessId": "ORD-1001", "state": "DRAFT", "event": "APPROVE"}} - 避免在 controller 层 try-catch 后自行封装响应,破坏分层职责
- 若需区分不同业务场景,可用不同异常子类(如
OrderInvalidTransitionException、PaymentInvalidTransitionException),但都继承自同一基类并统一处理
异常类型选择要符合语义与传播预期
状态迁移失败是典型的业务约束违规,不是系统故障,应使用 unchecked 异常。
- 继承
RuntimeException,无需强制 try-catch,调用方按需决策是否捕获 - 不要用
Exception或其子类(如IOException),避免编译器强制处理,违背业务异常设计原则 - 不建议在事务方法中对这类异常配置
noRollbackFor——非法状态迁移本就不该提交,回滚是合理默认行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










