视频看了几百小时还迷糊?关注我,几分钟让你秒懂!
在 Java 高级工程师的面试中,“如何统一处理异常”几乎是必问的问题。尤其是在使用 Spring Boot 开发微服务时,良好的异常处理机制不仅能提升系统健壮性,还能让前端获得清晰、一致的错误信息。
本文将从实际需求场景出发,用通俗易懂的方式带你掌握 Spring Boot 中的全局异常处理(@ControllerAdvice + @ExceptionHandler),并附上正例、反例和注意事项,小白也能轻松理解!
一、需求场景
假设你正在开发一个用户管理的 RESTful API:
- 前端调用
/api/user/{id}获取用户信息; - 如果
id不存在,后端应返回404 Not Found; - 如果数据库连接失败,应返回
500 Internal Server Error; - 所有错误都应以统一 JSON 格式返回,比如:
{ "code": 404, "message": "用户不存在", "timestamp": "2025-12-25T12:00:00" }问题来了:如果每个 Controller 都手动 try-catch,代码会非常冗余且难以维护!
二、解决方案:使用 @ControllerAdvice 实现全局异常处理
✅ 正确做法(推荐)
1. 定义统一响应格式
// CommonResult.java public class CommonResult<T> { private int code; private String message; private T data; private String timestamp; // 构造方法 & Getter/Setter 省略,可用 Lombok 简化 public static <T> CommonResult<T> error(int code, String message) { CommonResult<T> result = new CommonResult<>(); result.code = code; result.message = message; result.timestamp = java.time.LocalDateTime.now().toString(); return result; } }2. 自定义业务异常类(可选但推荐)
// BusinessException.java public class BusinessException extends RuntimeException { private final int code; public BusinessException(int code, String message) { super(message); this.code = code; } public int getCode() { return code; } }3. 全局异常处理器
// GlobalExceptionHandler.java @RestControllerAdvice public class GlobalExceptionHandler { // 处理自定义业务异常 @ExceptionHandler(BusinessException.class) public ResponseEntity<CommonResult<Void>> handleBusinessException(BusinessException ex) { return ResponseEntity.status(ex.getCode()) .body(CommonResult.error(ex.getCode(), ex.getMessage())); } // 处理资源未找到(如路径参数错误) @ExceptionHandler(NoSuchElementException.class) public ResponseEntity<CommonResult<Void>> handleNotFound(Exception ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(CommonResult.error(404, "资源不存在")); } // 捕获所有未处理的异常(兜底) @ExceptionHandler(Exception.class) public ResponseEntity<CommonResult<Void>> handleUnexpectedError(Exception ex) { // 实际项目中应记录日志! ex.printStackTrace(); // 仅演示,生产环境用 log.error() return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(CommonResult.error(500, "服务器内部错误,请稍后再试")); } }4. Controller 示例
@RestController @RequestMapping("/api/user") public class UserController { @GetMapping("/{id}") public CommonResult<User> getUser(@PathVariable Long id) { if (id <= 0) { throw new BusinessException(400, "用户ID无效"); } if (id == 999) { throw new NoSuchElementException("用户不存在"); } // 模拟正常返回 User user = new User(id, "张三"); return CommonResult.success(user); } }💡
CommonResult.success()方法可自行补充,用于封装成功响应。
三、反例(千万别这么写!)
❌ 反例1:每个方法都 try-catch
@GetMapping("/{id}") public ResponseEntity<?> getUserBad(@PathVariable Long id) { try { if (id <= 0) throw new IllegalArgumentException("无效ID"); // ...业务逻辑 return ResponseEntity.ok(...); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of("error", "服务器错误")); } }问题:
- 代码重复,违反 DRY 原则;
- 错误格式不统一;
- 难以维护,新增异常类型需修改多处。
❌ 反例2:只捕获 Exception,忽略具体类型
@ExceptionHandler(Exception.class) public ResponseEntity<?> handleAll(Exception e) { return ResponseEntity.status(500).body("出错了"); }问题:
- 无法区分 400、404、500 等不同错误码;
- 前端无法做针对性处理;
- 用户体验差。
四、注意事项(面试加分项!)
@ControllerAdvice vs @RestControllerAdvice
@ControllerAdvice:配合@ResponseBody使用才能返回 JSON;@RestControllerAdvice = @ControllerAdvice + @ResponseBody,更简洁,推荐使用。
异常处理顺序很重要!
Spring 会优先匹配最具体的异常类型。所以:- 先写
BusinessException - 再写
NoSuchElementException - 最后写
Exception(兜底)
- 先写
务必记录日志!
在handleUnexpectedError中一定要用log.error("系统异常", ex)记录堆栈,方便排查问题。不要暴露敏感信息
生产环境中,不要直接返回ex.getMessage()或堆栈信息,防止信息泄露。结合 Validation 使用
对于参数校验,可配合@Valid和MethodArgumentNotValidException统一处理校验错误。
五、总结
| 优点 | 说明 |
|---|---|
| ✅ 代码解耦 | 异常处理与业务逻辑分离 |
| ✅ 统一格式 | 所有接口返回一致的错误结构 |
| ✅ 易于维护 | 新增异常只需加一个@ExceptionHandler |
| ✅ 提升体验 | 前端可根据 code 做不同提示 |
掌握这套全局异常处理机制,不仅能让你的代码更专业,在面试中也能展现出工程化思维和系统设计能力!
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!