java多重catch需按子类到父类顺序排列,推荐合并语义相近异常;业务日志须含动作、用户id、traceid等上下文字段并脱敏;spring应用应使用@controlleradvice统一处理异常,避免手动try-catch。

Java 里用多重 catch 捕获不同异常类型
Java 7+ 支持在一个 try 块后跟多个 catch,每个捕获一种(或多种用 | 分隔的)具体异常类型。这是最直接、类型安全的方式,避免用泛化 Exception 掩盖差异。
常见错误是把子类异常写在父类后面,比如先写 catch (Exception e) 再写 catch (IOException e) —— 编译器会报错:「exception IOException has already been caught」。
- 按从具体到宽泛顺序排列:
catch (SQLException e)→catch (IOException e)→catch (RuntimeException e) - 同一
catch中合并处理语义相近的异常:catch (IOException | SQLException e) - 不要在每个
catch里重复写日志逻辑;提取共用方法,传入异常和业务上下文
统一记录业务错误日志的关键字段怎么塞进去
单纯记 e.printStackTrace() 或 e.getMessage() 对排查业务问题基本没用。真正有用的日志必须带上下文:当前操作、用户 ID、请求 ID、关键参数值。
推荐做法是在 catch 块里构造一个结构化日志对象(哪怕只是 Map),再交给统一日志方法。别依赖异常本身的字段——SQLException.getSQLState() 和 HttpClientErrorException.getStatusCode() 完全不是一回事。
- 必填字段建议:业务动作(如
"create_order")、用户标识(userId)、请求唯一 ID(traceId)、原始异常类名(e.getClass().getSimpleName())、精简消息(e.getMessage(),长度截断至 200 字以内) - 敏感字段如密码、token、完整 SQL 要过滤,可用正则或白名单方式脱敏
- 避免在日志里调用
e.printStackTrace()输出堆栈——除非明确需要,否则用logger.error(msg, e)让日志框架自己处理
Spring 的 @ControllerAdvice 怎么替代手写 catch
如果你用 Spring MVC 或 WebFlux,硬编码一堆 try-catch 是反模式。全局异常处理器能集中拦截、分类、记录,还能统一返回格式。
注意 @ExceptionHandler 方法的参数顺序和类型匹配规则:它只接收当前 controller 层抛出的异常,且优先匹配最具体的注解。比如 @ExceptionHandler(BusinessException.class) 不会捕获被 @ResponseStatus 包装过的子类,除非显式声明。
- 定义自定义异常基类(如
BusinessException),所有业务异常继承它,便于统一识别 - 在
@ExceptionHandler方法里调用统一日志方法,并传入WebRequest或HttpServletRequest获取请求上下文 - 慎用
@ExceptionHandler(Exception.class):它会兜底所有未被处理的异常,包括NullPointerException,容易掩盖开发问题
日志内容被吞掉或格式混乱的几个典型原因
明明写了日志,线上却查不到或字段错位,大概率不是代码逻辑问题,而是日志框架配置或调用姿势不对。
比如 SLF4J + Logback 场景下,用 logger.error("msg: {}, err: {}", msg, e) 是对的,但写成 logger.error("msg: " + msg + ", err: " + e) 就会丢失堆栈;又或者异步线程中没传递 MDC(Mapped Diagnostic Context)导致 traceId 为空。
- MDC 数据不会自动跨线程继承,使用线程池时需手动
MDC.getCopyOfContextMap()传递 - 检查日志级别是否被设为
WARN或更高,导致ERROR日志被过滤 - 避免在
catch里吞掉异常又不抛出或返回错误码,比如只写log.error(...); return null;—— 调用方无法感知失败










