news 2026/5/25 20:03:48

手把手教你封装一个异常处理类(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/5/23 17:35:53

基础架构即代码?不,Sealos 让基础架构变成了开箱即用

当整个行业还在写 YAML&#xff0c;我们已经在思考下一个十年说句得罪人的话&#xff1a;基础设施即代码&#xff08;IaC&#xff09;是个伟大的进步&#xff0c;但它正在成为新的技术债。一个反直觉的观察这两年我见了太多团队&#xff0c;他们的 Terraform 文件比业务代码还多…

作者头像 李华
网站建设 2026/5/22 19:47:49

天辛大师也谈AI时代的社会心理学:I人与E人命运谱系

备受瞩目的社会心理学家天辛大师近日在一场深度访谈中&#xff0c;将研究的目光投向了MBTI人格类型理论中两个核心维度——内倾&#xff08;I&#xff09;与外倾&#xff08;E&#xff09;&#xff0c;并开创性地提出了“AI时代I人与E人命运谱系”这一引人深思的命题。他指出&a…

作者头像 李华
网站建设 2026/5/15 5:41:17

Nodejs+vue微信小小程序 Android 的私人身体心理健康测试系统-vue

文章目录 技术栈概述核心功能模块跨平台适配方案数据安全与隐私性能优化策略扩展性设计 --nodejs技术栈--结论源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 技术栈概述 Node.js 作为后端服务&#xff0c;提供 RESTful API 接口&…

作者头像 李华
网站建设 2026/5/25 11:49:12

Nodejs+vue微信小程序 游天下旅游酒店预订移动端系统_d72md-vue

文章目录游天下旅游酒店预订移动端系统概述技术架构核心功能模块特色亮点部署与扩展--nodejs技术栈--结论源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;游天下旅游酒店预订移动端系统概述 该系统基于Node.js后端与Vue.js前端技术栈开…

作者头像 李华
网站建设 2026/5/9 17:03:13

AI生成测试用例的“可维护性”:代码能跑,但谁看得懂?

AI测试用例的双刃剑 在2026年的软件测试领域&#xff0c;AI生成测试用例已成为提升效率的利器&#xff0c;它能自动生成可运行的代码脚本&#xff0c;大幅缩短测试周期。然而&#xff0c;从业者常面临一个尴尬现实&#xff1a;生成的代码虽然能“跑”&#xff0c;却像一本天书…

作者头像 李华