news 2026/4/18 5:18:52

手把手教你封装一个异常处理类(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/4/16 15:57:51

msvcr80d.dll文件丢失找不到问题 免费下载方法分享

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/4/15 15:12:05

Spring AOP 如何切入抽象类?抽象类方法能被增强吗

在Spring AOP的实际应用中&#xff0c;抽象类的切面处理是一个容易被忽视但又至关重要的技术点。许多开发者在使用AOP时&#xff0c;常常会遇到抽象类中的方法无法被正常增强的情况&#xff0c;这不仅影响代码的预期行为&#xff0c;也可能导致日志记录、事务管理等横切关注点失…

作者头像 李华
网站建设 2026/4/16 0:09:35

超越Chat界面:深入Ollama本地模型API,构建生产级AI微服务

好的&#xff0c;这是根据您的要求生成的一篇关于Ollama本地模型API的技术深度文章。文章围绕如何使用其API构建高效、可扩展的本地AI应用展开&#xff0c;包含了高级用法和性能考量。 超越Chat界面&#xff1a;深入Ollama本地模型API&#xff0c;构建生产级AI微服务 在人工智…

作者头像 李华
网站建设 2026/3/1 16:02:18

UVa 146 ID Codes

题目描述 在 208420842084 年&#xff0c;政府为了加强对公民的控制并应对长期存在的法律与秩序问题&#xff0c;决定采取一项激进措施&#xff1a;为每位公民在左手腕植入一个微型计算机。该计算机存储个人身份信息&#xff0c;并包含发射器以便中央计算机追踪和监控人员的位…

作者头像 李华
网站建设 2026/4/16 10:59:10

‌5个开源项目:构建你的私人测试AI

AI驱动的测试革命‌ 在2026年&#xff0c;软件测试行业正经历一场由人工智能主导的变革。传统手动测试效率低下、维护成本高&#xff0c;而开源AI工具通过自动化、智能化和自愈能力&#xff0c;让测试从业者能构建私人测试AI系统&#xff0c;实现“一人一AI”的高效工作流。这…

作者头像 李华