自定义异常应继承RuntimeException以触发自动事务回滚,并携带errorCode、context等可追溯字段;需重写toString()确保日志可见,通过全局ExceptionHandler统一处理响应、日志与事务。

在复杂业务流程中,用自定义异常既触发事务回滚,又携带可追溯、可消费的上下文数据,关键不是“抛一个异常”,而是让这个异常成为业务语义、事务控制与可观测性之间的桥梁。它得被 Spring 识别为回滚信号,同时字段能进日志、进响应、进监控系统。
继承 RuntimeException,确保默认回滚生效
Spring 的事务管理器默认只对 RuntimeException 及其子类 自动回滚。如果自定义异常继承 Exception(受检异常),即使逻辑正确,事务也不会回滚——除非显式配置 @Transactional(rollbackFor = YourException.class),这增加了耦合和遗漏风险。
- 直接继承
RuntimeException,例如:public class InventoryShortageException extends RuntimeException - 避免使用
throws声明,不打断服务层代码流,也天然兼容声明式事务 - 若已有历史原因必须用受检异常,务必在所有涉及事务的方法上补全
rollbackFor,且需全局检查是否漏配
字段设计聚焦“回滚决策”与“事后归因”两类信息
不是所有上下文都该塞进异常;要区分哪些字段影响回滚行为,哪些用于出问题后定位。字段应精简、只读、可序列化。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
影响回滚判断的字段:一般不需要——Spring 只看异常类型,不看内容。但可约定某些 code 值(如
"CRITICAL_VALIDATION_FAIL")在全局处理器中主动调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),实现细粒度控制 -
支撑归因的字段(必须有):
-
errorCode:字符串枚举值(如"INVENTORY_INSUFFICIENT"),用于日志分类、ELK 聚合、前端提示映射 -
context:不可变Map<String, Object>,存orderId、skuId、expectedQty、availableQty等关键业务键值,不存大对象或流 -
requestId或traceId:便于关联链路日志,若已通过 MDC 注入,异常中可不重复存
-
构造与重写 toString(),让上下文真正在日志里可见
Logback/Log4j 默认只打印 toString(),而 Throwable 的默认实现不包含你加的字段。不重写,等于没加。
立即学习“Java免费学习笔记(深入)”;
- 所有含上下文的构造函数,最后必须调用
super(message)或super(message, cause),保留原始堆栈 - 重写
toString(),格式建议:super.toString() + "; code=" + errorCode + "; context=" + context,并做 null 安全处理 - 避免重写
getMessage()拼接上下文——它只用于摘要,且部分框架(如 Feign)可能依赖原始 message 做解析 - 加上
private static final long serialVersionUID = 1L;,保障序列化一致性
在全局处理器中联动事务、响应与日志
异常对象创建只是起点,真正价值在被消费时释放。全局 @ExceptionHandler 是统一出口。
- 响应层面:提取
ex.getErrorCode()和ex.getContext(),组装为标准 JSON(如{"code":"INVENTORY_INSUFFICIENT","message":"库存不足","data":null,"debug":{"orderId":"ORD-123","sku":"SKU-789"}}) - 日志层面:WARN 级打日志时,把
errorCode和context放入 MDC:MDC.put("err_code", ex.getErrorCode()); MDC.putAll(ex.getContext());,后续日志自动带上 - 事务层面:绝大多数情况无需干预——Spring 已根据异常类型完成回滚。仅当需基于字段做条件回滚(如某些 code 不回滚),才在 handler 中手动调用
setRollbackOnly()

















