当前位置: 首页 > news >正文

手把手教你封装一个异常处理类(Spring Boot 全局统一异常处理实战)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


🧩 一、为什么需要统一异常处理?

在没有统一异常处理前,你的代码可能是这样的:

@GetMapping("/user/{id}") public ResponseEntity<?> getUser(@PathVariable Long id) { try { User user = userService.findById(id); return ResponseEntity.ok(user); } catch (UserNotFoundException e) { return ResponseEntity.status(404).body("用户不存在"); } catch (Exception e) { log.error("查询用户出错", e); return ResponseEntity.status(500).body("系统繁忙"); } }

问题很明显

  • 每个接口都要写try-catch,重复代码多;
  • 错误信息不统一,前端解析困难;
  • 异常日志分散,排查问题难;
  • HTTP 状态码混乱(有的返回 200 却 body 是错误)。

目标
一处定义,全局生效;结构清晰,前后端解耦;日志完整,便于监控。


🛠️ 二、Spring Boot 正确姿势:@ControllerAdvice+@ExceptionHandler

1. 定义统一返回格式

import lombok.Data; import java.time.LocalDateTime; @Data public class ApiResult<T> { private int code; private String message; private T data; private LocalDateTime timestamp; public static <T> ApiResult<T> success(T data) { ApiResult<T> result = new ApiResult<>(); result.code = 200; result.message = "success"; result.data = data; result.timestamp = LocalDateTime.now(); return result; } public static <T> ApiResult<T> error(int code, String message) { ApiResult<T> result = new ApiResult<>(); result.code = code; result.message = message; result.timestamp = LocalDateTime.now(); return result; } }

💡 前端只需要判断code == 200就知道成功,无需解析 HTTP 状态码!


2. 自定义业务异常类

public class BusinessException extends RuntimeException { private final int code; public BusinessException(int code, String message) { super(message); this.code = code; } public BusinessException(String message) { this(400, message); // 默认 400 } public int getCode() { return code; } }

✅ 业务异常 vs 系统异常:

  • BusinessException:参数错误、余额不足等可预期错误;
  • RuntimeException:空指针、数据库连接失败等不可预期错误。

3. 全局异常处理器(核心!)

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); // 处理自定义业务异常 @ExceptionHandler(BusinessException.class) public ApiResult<?> handleBusinessException(BusinessException e, HttpServletRequest request) { log.warn("业务异常 | URI: {} | Error: {}", request.getRequestURI(), e.getMessage()); return ApiResult.error(e.getCode(), e.getMessage()); } // 处理参数校验异常(JSR-303) @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiResult<?> handleValidationException(MethodArgumentNotValidException e) { String message = e.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); log.warn("参数校验失败: {}", message); return ApiResult.error(400, "请求参数错误: " + message); } // 处理路径变量/请求参数类型转换错误 @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiResult<?> handleIllegalArgumentException(IllegalArgumentException e) { log.warn("非法参数: {}", e.getMessage()); return ApiResult.error(400, "参数格式错误"); } // 处理所有未捕获的系统异常(兜底) @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiResult<?> handleSystemException(Exception e, HttpServletRequest request) { // 生产环境不要暴露堆栈给前端! log.error("系统异常 | URI: {} | Error: {}", request.getRequestURI(), e.getMessage(), e); return ApiResult.error(500, "系统繁忙,请稍后再试"); } }

🔑 关键点:

  • @RestControllerAdvice=@ControllerAdvice+@ResponseBody
  • 按异常类型分层处理;
  • 生产环境绝不返回e.printStackTrace()给前端!

🧪 三、使用示例

1. 业务层抛出异常

@Service public class UserService { public User findById(Long id) { if (id <= 0) { throw new BusinessException("用户ID必须大于0"); } // ... 查询逻辑 if (user == null) { throw new BusinessException(404, "用户不存在"); } return user; } }

2. Controller 无需 try-catch

@RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public ApiResult<User> getUser(@PathVariable Long id) { User user = userService.findById(id); // 可能抛出 BusinessException return ApiResult.success(user); // 成功时直接返回 } }

3. 调用效果

  • 请求/api/user/0→ 返回:
{ "code": 400, "message": "用户ID必须大于0", "data": null, "timestamp": "2026-01-29T12:00:00" }
  • 请求/api/user/999(不存在)→ 返回:
{ "code": 404, "message": "用户不存在", "data": null, "timestamp": "2026-01-29T12:00:01" }

✅ 前端只需判断code,无需关心 HTTP 状态码!


❌ 四、反例 & 常见错误

反例 1:在 Controller 里到处写 try-catch

// ❌ 重复、难维护、风格不统一 @GetMapping("/user/{id}") public Object getUser(@PathVariable Long id) { try { // ... } catch (Exception e) { return Map.of("error", e.getMessage()); } }

反例 2:把系统异常堆栈返回给前端

@ExceptionHandler(Exception.class) public String handle(Exception e) { return e.toString(); // 😱 前端看到一长串堆栈! }

💥 风险:泄露服务器路径、框架版本、数据库结构!


反例 3:所有异常都返回 200

// ❌ HTTP 状态码始终是 200,违背 RESTful 原则 public Map<String, Object> error(String msg) { return Map.of("code", 500, "msg", msg); }

✅ 正确:HTTP 状态码 + JSON body 双重语义


⚠️ 五、注意事项 & 最佳实践

场景建议
敏感信息日志可打 full stack,但返回给前端的 message 要脱敏
国际化message可根据Accept-Language动态切换
监控告警5xx异常接入 Prometheus + AlertManager
区分环境开发环境可返回详细错误,生产环境必须隐藏
异常分类建议定义ClientException(4xx)和ServerException(5xx)

🎯 六、总结:统一异常处理架构图

Controller 方法 ↓ 抛出 BusinessException / ValidationException / Exception ↓ GlobalExceptionHandler 拦截 ↓ 按类型返回标准化 ApiResult ↓ 前端统一处理 code == 200 ?

好处:

  • ✅ 代码简洁,专注业务;
  • ✅ 错误格式统一,前后端协作顺畅;
  • ✅ 日志完整,便于运维;
  • ✅ 符合 RESTful 规范。

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

http://www.jsqmd.com/news/316957/

相关文章:

  • 我把 AWS 账单和 Sealos 账单放一起,老板沉默了
  • 手把手教你封装一个树形结构处理类(Java 通用 Tree 工具,支持无限层级)
  • 一文带你掌握 Nginx 配置:从零搭建高性能 Web 服务(附实战案例)
  • 手把手教你设计一个提供给三方调用的接口鉴权(含完整 Java + Spring Boot 实现)
  • 手把手教你封装一个调用三方接口的 HTTP 工具类(Spring Boot + Java 实战)
  • 【计算机网络】ep1:物理层概述
  • Spring Boot 中如何使用自定义配置管理器?告别硬编码,优雅管理你的配置!
  • Nacos 工作原理你真的了解吗?从配置拉取到动态刷新,一文讲透!
  • 【计算机网络】ep0:计算机网络概述
  • Nacos 你真的了解吗?Spring Boot 集成配置中心实战指南(小白也能看懂!)
  • 枚举类 enum class:强类型枚举的优势
  • CTF之——密码破解工具hashcat,零基础入门到精通,看完这篇就足够了~
  • 国内AI编程IDE对比(二):从零构建桌面应用实测
  • Java类型转换
  • 对目前C++方向的一些想法
  • 基础架构即代码?不,Sealos 让基础架构变成了开箱即用
  • 我终于不用在周末处理集群故障了,感谢 Sealos 的架构设计
  • 【26美赛B题】2026美赛数学建模(MCM/ICM)思路解析及代码分享
  • 【26美赛C题】2026美赛数学建模(MCM/ICM)思路解析及代码分享
  • msvcr80d.dll文件丢失找不到问题 免费下载方法分享
  • 国产化系统中WebUploader如何处理局域网大文件断点续传?
  • 百度开源上传组件在局域网如何处理大文件断点续传?
  • 局域网内WebUploader怎样支持大文件分段与断点续传?
  • 浙江万全扑克有限公司 联系方式:背景与联系信息参考
  • 2025环境试验设备厂商大比拼,口碑出炉,盐水喷雾试验箱及各种老化房,环境试验设备生产厂家排行榜
  • 聊聊杭州比较不错的职业装定制专业公司,哪家性价比高看这里
  • 聊聊煜形象个人西服定制职业装定制靠不靠谱,费用多少
  • 聊聊印刷胶辊定制厂家,泰兴金茂辊业区口碑如何
  • 浙江万全扑克有限公司 联系方式:产品选购与使用通用指南
  • 驰创轴承性价比怎么样,看它口碑与核心竞争力表现