news 2026/11/2 5:50:51

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你封装一个异常处理类(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 规范。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 5:39:42

【26美赛C题】2026美赛数学建模(MCM/ICM)思路解析及代码分享

【26美赛C题】2026美赛数学建模赛&#xff08;MCM/ICM&#xff09;思路解析及代码分享 订阅即可获取2026年及历年数学建模笔记&#xff0c;万字题解内容&#xff0c;且结合全球最新AI技术辅助&#xff0c;帮你轻松攻坚竞赛&#xff01;后续还将持续发布华为杯、高教社杯、华数杯…

作者头像 李华
网站建设 2026/11/1 5:39:38

BurpSuite入门及详细使用教程(内附学习笔记)

Burp Suite是用于攻击web应用程序的集成平台&#xff0c;接下来通过本文给大家介绍Burpsuite入门及使用详细教程&#xff0c;感兴趣的朋友一起看看吧。&#xff08;片尾有惊喜哦~&#xff09; 1、简介 Burp Suite是用于攻击web应用程序的集成平台。它包含了许多工具&#xff…

作者头像 李华
网站建设 2026/11/1 5:39:35

为什么说运维工程师做不长久,做两年就赶快转网络安全或者研发

很多从事IT网络运维工作的年轻小伙伴都会有个疑问&#xff0c;自己做的工作很杂似乎很基础&#xff0c;而且重复很多年&#xff0c;究竟有没前途。 作为过来人告诉一个总结&#xff1a;前途大小&#xff0c;工资多少跟你的岗位和职称资质没有多少关系&#xff0c;跟你的经验技能…

作者头像 李华
网站建设 2026/11/2 3:18:01

云平台安全风险聚焦:谷歌云生态正成为网络攻击的“高风险区”

CyCognito最新研究报告显示&#xff0c;在云服务商安全状况对比中&#xff0c;谷歌云及部分中小云平台存在漏洞的资产比例显著高于亚马逊云服务&#xff08;AWS&#xff09;和微软Azure。这项基于近500万个互联网暴露资产的研究发现&#xff0c;随着多云环境复杂性提升和在线资…

作者头像 李华
网站建设 2026/11/2 3:18:01

74页可编辑PPT | 数据架构设计总体规划方案

很多公司做报表时&#xff0c;同一个客户名字在不同系统里写法不同。数据拿不准&#xff0c;领导不敢用。系统越建越多&#xff0c;数据却越存越乱。主数据没人统一&#xff0c;口径对不上&#xff0c;分析结果相差大。数据质量没人考核&#xff0c;错误反复出现。 方案介绍这…

作者头像 李华
网站建设 2026/11/2 3:31:00

如何用Notion管理AI测试项目?2026年模板

AI测试管理的变革与Notion的核心价值 在2026年&#xff0c;AI测试已成为软件开发生命周期的关键环节&#xff0c;但传统工具难以应对动态需求。Notion作为集成数据库、文档和AI的协作平台&#xff0c;解决了测试管理的核心痛点&#xff1a;用例版本混乱导致回归测试失误&#…

作者头像 李华