受检异常是业务上下文的信号传递机制,要求消息嵌入业务标识(如订单号、用户名)、保留完整异常链、对外脱敏并结构化响应,以支撑精准决策与问题定位。

受检异常不是错误通知,而是业务上下文的信号传递机制。它要求调用方感知“某件事可能因外部条件失败”,并基于当前上下文做决策——这个决策能力,依赖于异常中携带的准确、可定位、可脱敏的上下文信息。
业务标识必须嵌入异常消息
异常消息不能停留在“操作失败”层面,而要包含能锚定具体业务实例的关键字段。比如注册场景下,“用户名已被占用”不如“用户名 'jack2026' 已被占用”明确;订单扣减失败时,“库存不足”需补充为“订单 order-20260619-789 库存校验失败,SKU-1002 当前可用量为 0”。这些字段让前端可直接展示、日志可精准过滤、监控可自动告警。
- 推荐提取:主键ID、业务单号、用户ID、资源编码、时间戳片段
- 避免硬编码路径、数据库表名、内部枚举值(如“status=3”应转为“订单状态非待支付”)
异常链必须完整保留原始根源
包装异常时若丢弃 cause,就等于切断了问题溯源路径。DAO 层捕获 SQLException 后,应通过 new DataAccessException("订单保存失败", sqlEx) 构造;Service 层再封装为 BusinessException 时,仍需传入该 DataAccessException 作为 cause。这样堆栈里既有 JDBC 驱动层的 SQL 错误,也有业务层的语义解释,运维查日志时能一层层展开,而不是只看到顶层“业务异常”四个字。
- 禁止字符串拼接掩盖原始异常,例如:
"订单保存失败: " + e.getMessage() - 禁止忽略 cause 参数,哪怕只是想加个前缀,也要用带 cause 的构造器
对外响应需剥离技术上下文,保留业务上下文
Controller 层收到受检异常后,不直接返回异常对象,而是在统一处理器中提取其业务含义,转为结构化响应。此时原始异常里的 SQL 状态码、文件系统路径、堆栈行号等全部过滤,但订单号、用户昵称、失败动作类型(如“创建”“修改”“删除”)必须保留在 message 或扩展字段中。
- 响应体示例:
{"code":"ORDER_STOCK_SHORTAGE","message":"订单不可提交:商品库存不足","orderNo":"order-20260619-789","requestId":"req-1a2b3c"} - message 面向用户,不含技术术语;orderNo 等字段面向后台排查,不暴露给前端界面
上下文传递不是堆栈记录,而是意图对齐
throw 一个受检异常,本质是在方法签名中声明:“我做的事依赖外部状态,出问题时你得知道是哪笔业务、为什么失败、能否重试”。所以上下文信息的设计,始终围绕三个问题:这是谁的操作?在什么实体上发生的?失败的具体约束条件是什么?不是为了填满字段,而是让接收方一眼明白“我现在该做什么”。
- 不写“请检查数据库连接”,而写“用户注册失败:数据库写入超时(timeout=3s)”
- 不抛 IOException,而抛封装后的 UserImportException,并附带文件名、第 127 行、字段 email 格式错误

















