一、基础原理:@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. 分层异常体系设计(核心亮点)
普遍采用 自定义异常分层模型 ,避免直接抛出 RuntimeException 或 Exception。
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_FOUNDvsPERMISSION_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 场景 |