在catch块中应记录带业务标识的上下文信息、保留原始异常链、按职责决定异常流向,避免字符串拼接、空catch、finally抛异常及重复包装。

在catch块中有效处理异常上下文,核心是让日志可追溯、语义可理解、行为可预期——不是只记下“出错了”,而是说清“谁在什么情况下因何失败”。
记录带业务标识的上下文信息
堆栈本身不说明业务场景。必须主动注入关键变量,如订单号、用户ID、请求路径、时间戳等。
- 用日志框架的占位符格式(如SLF4J):logger.error("支付回调验签失败,订单号:{},商户号:{},来源IP:{}", orderNo, merchantId, remoteIp, e);
- 避免拼接字符串:不要写 logger.error("支付回调验签失败,订单号:" + orderNo + ",异常:" + e.getMessage()); —— 会丢失堆栈,且e.getMessage()可能为空
- 敏感字段(如身份证号、银行卡号)需脱敏后再记录
保留原始异常链不中断
上下文不只是文字,更是调用链路。丢掉cause就等于切断故障根因线索。
- 封装新异常时,必须使用带Throwable参数的构造器:throw new PaymentException("验签失败", e);
- 禁止只传消息:throw new PaymentException("验签失败: " + e.getMessage()); —— 堆栈全丢,排查只能靠猜
- 若需补充信息,可用addSuppressed()追加辅助异常(如清理资源时发生的次生异常)
按场景决定是否继续抛出
上下文处理完后,不能默认“吞掉”或“硬抛”,要结合当前层职责判断流向。
立即学习“Java免费学习笔记(深入)”;
- 能兜底恢复(如加载配置失败→切默认值),就记录+返回默认值,不抛出
- 属于本层不可控问题(如数据库连接断开),应包装为更上层能理解的业务异常再抛出
- 已记录且完成资源清理(如关闭流、回滚事务),可直接重抛原异常:throw e;,不改语义
避免上下文被覆盖或污染
finally里抛异常、空catch、多层重复包装,都会让原始上下文失真或湮没。
- finally中禁止抛异常:它会覆盖catch中已准备好的throw,真实错误被静默替换
- 不写空catch:catch (Exception e) { throw e; } 没日志、没上下文、没清理,等于徒增try-catch层数
- 同一业务异常避免跨层重复包装:Service已抛OrderCreateException,Controller不应再包成ApiException,而应直接响应或转换状态码


















