Spring Boot全局异常处理实战指南
作者:白仑色
在开发过程中,异常是不可避免的,Spring Boot提供了一种简单而强大的机制来处理应用程序中的异常,即全局异常处理,这篇文章主要介绍了Spring Boot全局异常处理的相关资料,需要的朋友可以参考下
引言
在开发 Spring Boot 项目时,你是否遇到过这些问题?
- ❌ 不同 Controller 重复写
try-catch
- ❌ 异常信息格式不统一,前端难以解析
- ❌ 系统内部错误直接暴露给用户(如堆栈信息)
- ❌ 404、500 等状态码处理混乱
这些问题不仅影响用户体验,还可能带来安全风险。
全局异常处理就是 Spring Boot 为我们提供的“统一调度中心”,它能在异常发生时自动拦截、处理,并返回友好的响应。
本文将带你从零开始,构建一套生产级的全局异常处理机制,并结合实际代码,让你彻底掌握这一核心技能。
一、为什么需要全局异常处理?
想象一个电商系统:
- 用户下单时库存不足 → 返回“库存不足,请稍后再试”
- 订单ID格式错误 → 返回“订单不存在”
- 数据库连接失败 → 记录日志,返回“系统繁忙,请稍后重试”
如果没有统一处理,每个接口都要写类似的 try-catch
,代码重复且难以维护。
而有了全局异常处理,就像设立了一个“客户服务中心”,所有异常都由它统一接待、分类处理、礼貌回应。
二、核心技术:@ControllerAdvice和@ExceptionHandler
Spring Boot 提供了两个核心注解来实现全局异常处理:
注解 | 作用 |
---|---|
@ControllerAdvice | 定义全局异常处理器(可作用于所有 Controller) |
@ExceptionHandler | 指定处理某类异常的方法 |
三、实战:构建统一异常处理机制
3.1 定义统一返回格式
@Data @AllArgsConstructor @NoArgsConstructor public class ApiResponse<T> { private int code; private String message; private T data; // 成功响应 public static <T> ApiResponse<T> success(T data) { return new ApiResponse<>(200, "success", data); } // 失败响应 public static <T> ApiResponse<T> error(int code, String message) { return new ApiResponse<>(code, message, null); } }
3.2 自定义业务异常
// 业务异常基类 public class BusinessException extends RuntimeException { private int code; public BusinessException(int code, String message) { super(message); this.code = code; } public BusinessException(String message) { super(message); this.code = 500; } // getter public int getCode() { return code; } }
使用示例:
@Service public class OrderService { public void createOrder(Long productId, Integer quantity) { if (quantity <= 0) { throw new BusinessException(400, "购买数量必须大于0"); } // ... 其他逻辑 } }
3.3 全局异常处理器(核心)
@RestControllerAdvice // 等价于 @ControllerAdvice + @ResponseBody @Slf4j public class GlobalExceptionHandler { /** * 处理自定义业务异常 */ @ExceptionHandler(BusinessException.class) public ApiResponse<String> handleBusinessException(BusinessException e) { log.warn("业务异常: {}", e.getMessage()); return ApiResponse.error(e.getCode(), e.getMessage()); } /** * 处理参数校验异常(@Valid) */ @ExceptionHandler(MethodArgumentNotValidException.class) public ApiResponse<String> handleValidationException(MethodArgumentNotValidException e) { // 获取第一个错误信息 String errorMessage = e.getBindingResult() .getFieldErrors() .stream() .map(FieldError::getDefaultMessage) .findFirst() .orElse("参数校验失败"); log.warn("参数校验异常: {}", errorMessage); return ApiResponse.error(400, errorMessage); } /** * 处理空指针异常 */ @ExceptionHandler(NullPointerException.class) public ApiResponse<String> handleNullPointerException(NullPointerException e) { log.error("空指针异常", e); return ApiResponse.error(500, "系统内部错误,请联系管理员"); } /** * 处理所有未被捕获的异常(兜底) */ @ExceptionHandler(Exception.class) public ApiResponse<String> handleException(Exception e) { log.error("未处理异常", e); return ApiResponse.error(500, "系统繁忙,请稍后重试"); } /** * 处理 404 Not Found */ @ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiResponse<String> handle404(NoHandlerFoundException e) { log.warn("请求路径不存在: {}", e.getRequestURL()); return ApiResponse.error(404, "请求的资源不存在"); } }
3.4 启用 404 异常捕获(重要!)
默认情况下,404 异常不会进入 @ExceptionHandler
,需在配置文件中开启:
# application.yml spring: mvc: throw-exception-if-no-handler-found: true # 找不到处理器时抛出异常 web: resources: add-mappings: false # 关闭默认静态资源映射(可选,更严格)
四、测试验证
4.1 创建测试 Controller
@RestController @RequestMapping("/api") public class TestController { @GetMapping("/business") public ApiResponse<String> businessError() { throw new BusinessException(400, "用户名已存在"); } @PostMapping("/validate") public ApiResponse<String> validate(@Valid @RequestBody UserForm form) { return ApiResponse.success("验证通过"); } @GetMapping("/null") public ApiResponse<String> nullPointer() { String str = null; str.length(); // 触发 NullPointerException return ApiResponse.success("success"); } @GetMapping("/unknown") public ApiResponse<String> unknown() { throw new RuntimeException("未知错误"); } }
class UserForm { @NotBlank(message = "用户名不能为空") private String username; @Min(value = 18, message = "年龄不能小于18岁") private Integer age; // getter & setter }
4.2 测试结果
请求 | 响应 |
---|---|
GET /api/business | {"code":400,"message":"用户名已存在","data":null} |
POST /api/validate (无参数) | {"code":400,"message":"用户名不能为空","data":null} |
GET /api/null | {"code":500,"message":"系统内部错误,请联系管理员","data":null} |
GET /api/unknown | {"code":500,"message":"系统繁忙,请稍后重试","data":null} |
GET /api/not-exist | {"code":404,"message":"请求的资源不存在","data":null} |
五、高级技巧与生产实践
5.1 异常分类管理
你可以为不同模块定义不同的异常处理器:
// 用户模块异常处理器 @ControllerAdvice("com.example.controller.user") public class UserExceptionHandler { ... } // 订单模块异常处理器 @ControllerAdvice("com.example.controller.order") public class OrderExceptionHandler { ... }
5.2 结合 AOP 记录异常日志
@Aspect @Component public class ExceptionLogAspect { @AfterThrowing(pointcut = "@within(org.springframework.web.bind.annotation.RestController)", throwing = "ex") public void logException(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); log.error("方法 {} 发生异常: {}", methodName, ex.getMessage()); } }
5.3 返回错误码枚举(推荐)
public enum ErrorCode { SUCCESS(200, "成功"), BAD_REQUEST(400, "请求参数错误"), UNAUTHORIZED(401, "未授权"), FORBIDDEN(403, "禁止访问"), NOT_FOUND(404, "资源不存在"), SERVER_ERROR(500, "系统内部错误"); private final int code; private final String message; ErrorCode(int code, String message) { this.code = code; this.message = message; } // getter }
使用:
throw new BusinessException(ErrorCode.BAD_REQUEST);
总结:全局异常处理的最佳实践
实践 | 说明 |
---|---|
✅ 使用 @RestControllerAdvice | 统一返回 JSON 格式 |
✅ 自定义 BusinessException | 区分业务异常与系统异常 |
✅ 记录日志 | @Slf4j + log.error/warn |
✅ 敏感信息脱敏 | 不要将数据库错误、堆栈信息暴露给前端 |
✅ 合理分类异常 | 优先处理具体异常,最后是 Exception |
✅ 启用 404 捕获 | 配置 throw-exception-if-no-handler-found: true |
到此这篇关于Spring Boot全局异常处理的文章就介绍到这了,更多相关SpringBoot全局异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!