Spring Boot 的全局异常处理机制复习

一、基础原理:@ControllerAdvice + @ExceptionHandler

Spring Boot 基于 Spring MVC 提供了标准的全局异常处理机制:

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage(), e);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ErrorResponse.of(e.getCode(), e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) {
        String traceId = MDC.get("traceId"); // 关联链路追踪
        log.error("系统异常 [traceId={}]: {}", traceId, e.getMessage(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ErrorResponse.of("SYS_ERROR", "系统繁忙,请稍后再试"));
    }
}

✅ 核心注解:

  • @ControllerAdvice:对所有 @Controller 生效
  • @RestControllerAdvice:返回 JSON(等价于 @ControllerAdvice + @ResponseBody
  • @ExceptionHandler:捕获指定异常类型

二、实战中的关键实践(亮点)

1. 分层异常体系设计(核心亮点)

普遍采用 自定义异常分层模型 ,避免直接抛出 RuntimeExceptionException

java 复制代码
// 基础异常类
public abstract class BaseException extends RuntimeException {
    private final String code;
    public BaseException(String code, String message) { super(message); this.code = code; }
    public String getCode() { return code; }
}

// 业务异常(4xx)
public class BusinessException extends BaseException {
    public BusinessException(String code, String message) {
        super(code, message);
    }
}

// 系统异常(5xx)
public class SystemException extends BaseException {
    public SystemException(String message, Throwable cause) {
        super("SYS_ERROR", message);
        this.initCause(cause);
    }
}

优势

  • 前端可根据 code 做精准提示(如 USER_NOT_FOUND vs PERMISSION_DENIED
  • 监控系统可按异常码聚合告警
  • 避免暴露内部堆栈给用户

2. 统一响应体(Standardized Response)

所有接口返回结构一致,便于前端处理:

java 复制代码
public class ErrorResponse {
    private String code;      // 业务码,如 "ORDER_001"
    private String message;   // 用户友好提示
    private String traceId;   // 链路ID,用于日志追踪
    private long timestamp;

    public static ErrorResponse of(String code, String message) {
        ErrorResponse resp = new ErrorResponse();
        resp.code = code;
        resp.message = message;
        resp.traceId = MDC.get("traceId");
        resp.timestamp = System.currentTimeMillis();
        return resp;
    }
}

🌟 大厂要求:即使 5xx 错误,也不能返回裸露的 Tomcat 错误页或 JSON 格式不一致!

3. 日志与监控深度集成

(1)关联全链路追踪 ID
  • 利用 MDC(Mapped Diagnostic Context)注入 traceId
  • 异常日志中包含 traceId,可在 ELK / Sentry / ARMS 中快速定位请求上下文
(2)关键异常自动上报监控
java 复制代码
@ExceptionHandler(BusinessException.class)
public ResponseEntity<?> handle(...) {
    if (isCritical(e.getCode())) {
        metrics.counter("exception.business", "code", e.getCode()).increment();
        alarmService.sendAlert("业务异常激增: " + e.getCode()); // 接入企业微信/钉钉告警
    }
    // ...
}

💡 大厂通常会对接 Prometheus + Grafana内部监控平台,对异常进行量化分析。

4. 安全与信息脱敏

  • 绝不返回技术细节给前端(如数据库错误、文件路径)
  • 生产环境屏蔽 e.printStackTrace() 的原始信息
  • 敏感字段(如用户手机号、身份证)在日志中脱敏
java 复制代码
// 错误示例 ❌
return "数据库连接失败: " + e.getMessage(); // 可能泄露 IP、表名等

// 正确做法 ✅
return "服务暂时不可用,请联系客服"; // 用户友好 + 安全

5. 异步与非 Web 场景的异常处理

大厂系统大量使用异步任务、MQ 消费、定时任务,这些场景 不在 @ControllerAdvice 覆盖范围内

解决方案:

  • MQ 消费者:try-catch + 死信队列 + 告警

  • @Async 异步方法 :自定义 AsyncConfigurer 设置异常处理器

    java 复制代码
    @Override
    public AsyncTaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadFactory(r -> {
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler((thread, ex) -> 
                log.error("Async task failed", ex));
            return t;
        });
        return executor;
    }

⚠️ 面试高频考点:@ControllerAdvice 只对 Controller 层生效,对 Service、Async、MQ 无效!

三、高级设计亮点(面试加分项)

✅ 1. 异常码标准化管理

  • 使用枚举或配置中心管理异常码(如 Apollo/Nacos)
  • 支持多语言国际化(i18n)
java 复制代码
public enum ErrorCode {
    USER_NOT_FOUND("USER_001", "用户不存在"),
    ORDER_EXPIRED("ORDER_002", "订单已过期");

    private final String code;
    private final String msg;
}

✅ 2. 熔断与降级联动

  • 当某类异常频率突增,触发 Hystrix/Sentinel 熔断
  • 返回缓存数据或默认兜底结果,而非直接报错

✅ 3. 自动化测试覆盖

  • 单元测试验证异常路径(MockMvc 测试 4xx/5xx 响应)
  • Chaos Engineering(混沌工程)主动注入异常,验证系统韧性

四、常见面试题 & 回答思路

Q1:@ControllerAdvice 的原理是什么?

A:基于 Spring AOP 和 DispatcherServlet 的 HandlerExceptionResolver 机制。当 Controller 抛出异常时,DispatcherServlet 会遍历所有 HandlerExceptionResolver,其中 ExceptionHandlerExceptionResolver 会查找匹配的 @ExceptionHandler 方法并执行。

Q2:全局异常处理能捕获哪些异常?

A:只能捕获 Controller 层抛出的异常。对于 Filter、Interceptor、Async、MQ、定时任务中的异常,需要单独处理。

Q3:如何避免异常信息泄露?

A:生产环境关闭 Spring Boot 的 server.error.include-message=never,并在全局异常处理器中统一返回用户友好消息,原始堆栈仅记录到日志。

Q4:如何做异常监控?

A:结合 MDC 注入 traceId,异常日志接入 ELK;关键异常通过 Micrometer 上报指标到 Prometheus;高频异常触发企业告警(如钉钉机器人)。


总结:异常处理的核心思想

维度 做法
分层 自定义异常体系(业务/系统)
统一 标准响应格式 + 异常码
可观测 traceId + 日志 + 监控 + 告警
安全 不泄露技术细节,日志脱敏
健壮 覆盖异步/MQ/定时任务等非 Web 场景
相关推荐
源代码•宸几秒前
goframe框架签到系统项目开发(实现总积分和积分明细接口、补签日期校验)
后端·golang·postman·web·dao·goframe·补签
无限进步_6 分钟前
【C语言】堆(Heap)的数据结构与实现:从构建到应用
c语言·数据结构·c++·后端·其他·算法·visual studio
掉鱼的猫7 分钟前
灵动如画 —— 初识 Solon Graph Fluent API 编排
java·openai·workflow
初次攀爬者7 分钟前
基于知识库的知策智能体
后端·ai编程
喵叔哟7 分钟前
16.项目架构设计
后端·docker·容器·.net
强强强7958 分钟前
python代码实现es文章内容向量化并搜索
后端
周杰伦fans9 分钟前
AndroidStudioJava国内镜像地址gradle
android·java·android-studio
艾莉丝努力练剑9 分钟前
【Linux进程控制(一)】进程创建是呼吸,进程终止是死亡,进程等待是重生:进程控制三部曲
android·java·linux·运维·服务器·人工智能·安全
A黑桃11 分钟前
Paimon 表定时 Compact 数据流程与逻辑详解
后端
掘金者阿豪12 分钟前
JVM由简入深学习提升分(生产项目内存飙升分析)
后端