企业级Java项目统一异常处理的核心是三层协同:异常分层定义(ParamException/BusinessException/SystemException)、捕获位置收敛(Controller层外@ExceptionHandler全局拦截)、日志内容结构化(固定+动态上下文、敏感信息过滤、分级记录)。

企业级 Java 项目中,统一异常捕获与日志规范不是为了“消灭异常”,而是让异常可定位、可分类、可响应、可追溯。核心是三层协同:异常分层定义、捕获位置收敛、日志内容结构化。
定义清晰的异常分层体系
避免直接抛出 RuntimeException 或 Exception,按语义划分三级异常:
- ParamException:参数校验失败(如 @Valid 失败、必填字段为空),HTTP 状态码 400,前端可直接提示
- BusinessException:业务规则拒绝(如余额不足、重复下单、权限不足),HTTP 状态码 200 + 自定义错误码,返回用户友好提示
- SystemException:系统级故障(数据库连接中断、Redis 不可用、远程服务超时),HTTP 状态码 500,需记录完整堆栈并告警
所有自定义异常继承 RuntimeException,不强制上层 try-catch;必要时添加 code、message、cause 字段,便于序列化和前端识别。
统一在 Controller 层外拦截,禁用分散 try-catch
Spring Boot 项目必须使用 @ControllerAdvice 全局处理,而非在 service 或 dao 层手动捕获记录:
立即学习“Java免费学习笔记(深入)”;
- @ExceptionHandler 按异常类型精确匹配,顺序从具体到宽泛(如先 BusinessException,再 SystemException,最后兜底 Exception)
- 每个 @ExceptionHandler 方法内调用统一日志方法,传入 HttpServletRequest 获取 traceId、userId、请求路径等上下文
- 禁止在 service 方法里写 logger.error(...) —— 同一异常若被多层 catch,日志会重复且上下文错乱
日志内容必须带可操作上下文,不只记异常本身
error 日志不是 dump 堆栈,而是提供“谁、在哪、做了什么、出了什么问题”的最小闭环信息:
- 固定字段:业务动作(如 "pay_order")、用户 ID(脱敏显示为 u_abc123)、traceId(用于链路追踪)、异常简名(e.getClass().getSimpleName())、精简消息(截断至 200 字以内)
- 动态上下文:用 MDC.put() 提前注入关键变量(如 orderId、productId),后续所有日志自动携带,避免在每个 catch 里手动拼接
- 敏感信息必须过滤:密码、token、身份证号、完整 SQL 用正则或白名单方式替换,例如将 "password=123456" 替换为 "password=***"
区分日志级别,避免告警疲劳
同一异常类型在不同场景下日志级别应不同:
- 数据库连接池耗尽 → error(需立即介入)
- 调第三方接口超时但已启用本地缓存降级 → warn(记录但不告警)
- 用户提交手机号格式错误(前端已校验)→ info(仅用于行为审计,不进告警通道)
不在日志消息中调用 e.printStackTrace();始终用 logger.error("msg", e) 让日志框架处理堆栈;确保异常对象非 null 再传入,防止静默失败。


















