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

Spring Boot全局异常捕获指南

文章目录

    • 前言
    • 1. 为什么需要全局异常处理?
    • 2. 全局异常处理的三种实现方式
      • 2.1 @ControllerAdvice + @ExceptionHandler(推荐方式)
      • 2.2 使用@ErrorController自定义错误页面
      • 2.3 配置ErrorPageRegistrar
    • 3. 版本差异与兼容性问题
      • 3.1 Spring Boot 1.x vs 2.x vs 3.x
      • 3.2 处理版本差异的最佳实践
    • 4. 高级技巧与最佳实践
      • 4.1 异常处理优先级问题
      • 4.2 区分Web请求和API请求
      • 4.3 异常处理与国际化
    • 5. 测试全局异常处理
    • 6. 总结

前言

在Spring Boot开发中,优雅地处理异常是构建健壮应用的关键。本文将深入探讨全局异常处理的实现方式,并分析不同版本间的差异,助你打造更稳定的应用。

1. 为什么需要全局异常处理?

在Web应用开发中,异常处理往往是一个容易被忽视但却至关重要的环节。如果没有统一的异常处理机制,可能会出现以下问题:

  • 用户体验不一致:不同的异常显示不同的错误页面,甚至直接暴露堆栈信息
  • 代码重复:每个Controller都编写相似的异常处理代码
  • 维护困难:异常处理逻辑分散在各个角落,难以统一管理
  • 安全隐患:可能暴露系统内部细节给攻击者

Spring Boot通过提供全局异常处理机制,让我们能够以声明式的方式统一处理这些异常。

2. 全局异常处理的三种实现方式

2.1 @ControllerAdvice + @ExceptionHandler(推荐方式)

这是最常用且灵活的全局异常处理方式,适用于Spring 3.2及以上版本。

@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 处理业务异常*/@ExceptionHandler(BusinessException.class)public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {logger.error("业务异常: {}", ex.getMessage(), ex);ErrorResponse errorResponse = new ErrorResponse("BUSINESS_ERROR", ex.getMessage());return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);}/*** 处理数据不存在异常*/@ExceptionHandler(ResourceNotFoundException.class)public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex) {logger.error("资源未找到: {}", ex.getMessage(), ex);ErrorResponse errorResponse = new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage());return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);}/*** 处理所有未捕获的异常*/@ExceptionHandler(Exception.class)public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {logger.error("系统异常: {}", ex.getMessage(), ex);ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", "系统繁忙,请稍后再试");return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);}/*** 处理参数验证异常*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {logger.error("参数验证失败: {}", ex.getMessage(), ex);List<String> errors = ex.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());ErrorResponse errorResponse = new ErrorResponse("VALIDATION_ERROR", "参数验证失败", errors);return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);}
}// 统一的错误响应体
@Data
@AllArgsConstructor
@NoArgsConstructor
class ErrorResponse {private String code;private String message;private List<String> details;public ErrorResponse(String code, String message) {this.code = code;this.message = message;}
}

2.2 使用@ErrorController自定义错误页面

适用于需要自定义错误页面的场景,Spring Boot提供了BasicErrorController作为默认实现,我们可以继承并重写它。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CustomErrorController extends BasicErrorController {public CustomErrorController(ErrorAttributes errorAttributes) {super(errorAttributes, new ErrorProperties());}@Overridepublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));HttpStatus status = getStatus(request);// 自定义错误响应格式Map<String, Object> response = new HashMap<>();response.put("success", false);response.put("errorCode", body.get("status"));response.put("errorMessage", body.get("message"));response.put("timestamp", new Date());return new ResponseEntity<>(response, status);}
}

2.3 配置ErrorPageRegistrar

适用于需要根据不同HTTP状态码跳转到不同页面的场景。

@Configuration
public class CustomErrorPageConfiguration implements ErrorPageRegistrar {@Overridepublic void registerErrorPages(ErrorPageRegistry registry) {registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"),new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"),new ErrorPage(HttpStatus.FORBIDDEN, "/error/403"));}
}@Controller
@RequestMapping("/error")
public class ErrorPageController {@GetMapping("/404")public String notFound() {return "error/404";}@GetMapping("/500")public String serverError() {return "error/500";}@GetMapping("/403")public String forbidden() {return "error/403";}
}

3. 版本差异与兼容性问题

3.1 Spring Boot 1.x vs 2.x vs 3.x

特性Spring Boot 1.xSpring Boot 2.xSpring Boot 3.x
@ControllerAdvice支持支持支持
ErrorController接口有变化
默认错误处理BasicErrorControllerBasicErrorController基本保持一致
响应序列化Jackson 1.x/2.xJackson 2.xJackson 2.15+
路径匹配Ant路径匹配Ant路径匹配MVC路径匹配改进

重要变化:

  1. Spring Boot 2.3+:引入了ErrorProperties的构造方法变化,需要调整自定义ErrorController的实现
  2. Spring Boot 2.5+:改进了/error路径的处理逻辑,更加灵活
  3. Spring Boot 3.0+:Jakarta EE 9+,包名从javax变为jakarta

3.2 处理版本差异的最佳实践

// 兼容不同版本的ErrorController实现
@Controller
public class CompatibleErrorController {// 针对Spring Boot 2.3+的构造方法@Autowiredpublic CompatibleErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {// 实现逻辑}// 针对旧版本的备用构造方法@Autowired(required = false)public CompatibleErrorController(ErrorAttributes errorAttributes) {this(errorAttributes, new ErrorProperties());}
}

4. 高级技巧与最佳实践

4.1 异常处理优先级问题

当存在多个异常处理器时,Spring会按照以下优先级匹配:

  1. 最具体的异常类型优先
  2. 同一异常类型,在同一个@ControllerAdvice中按声明顺序
  3. 不同@ControllerAdvice之间,按@Order注解或Ordered接口实现排序
// 通过@Order控制处理顺序
@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice
public class HighPriorityExceptionHandler {// 处理高优先级异常
}@Order(Ordered.LOWEST_PRECEDENCE)
@RestControllerAdvice
public class LowPriorityExceptionHandler {// 处理低优先级异常和通用异常
}

4.2 区分Web请求和API请求

有时我们需要对Web页面请求和API请求返回不同的错误响应:

@RestControllerAdvice
public class ApiExceptionHandler {@ExceptionHandler(Exception.class)public ResponseEntity<ErrorResponse> handleApiException(Exception ex, HttpServletRequest request) {// 检查请求是否来自APIif (isApiRequest(request)) {// 返回JSON格式错误return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse("API_ERROR", ex.getMessage()));}// 非API请求,继续后续处理throw new RuntimeException(ex);}private boolean isApiRequest(HttpServletRequest request) {return request.getRequestURI().startsWith("/api/") ||"application/json".equals(request.getHeader("Accept"));}
}

4.3 异常处理与国际化

结合Spring的国际化支持,提供多语言错误信息:

@RestControllerAdvice
public class InternationalizedExceptionHandler {@Autowiredprivate MessageSource messageSource;@ExceptionHandler(BusinessException.class)public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex, Locale locale) {String localizedMessage = messageSource.getMessage(ex.getErrorCode(), ex.getArgs(), ex.getMessage(), locale);ErrorResponse errorResponse = new ErrorResponse(ex.getErrorCode(), localizedMessage);return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);}
}

5. 测试全局异常处理

确保异常处理逻辑正确工作的测试案例:

@SpringBootTest
@AutoConfigureMockMvc
public class GlobalExceptionHandlerTest {@Autowiredprivate MockMvc mockMvc;@Testpublic void testResourceNotFoundException() throws Exception {mockMvc.perform(get("/api/users/999")).andExpect(status().isNotFound()).andExpect(jsonPath("$.code").value("RESOURCE_NOT_FOUND")).andExpect(jsonPath("$.message").exists());}@Testpublic void testValidationException() throws Exception {UserRequest invalidRequest = new UserRequest("", "invalid-email");mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(invalidRequest))).andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value("VALIDATION_ERROR")).andExpect(jsonPath("$.details").isArray());}
}

6. 总结

Spring Boot的全局异常处理机制提供了强大而灵活的方式来统一处理应用中的异常。通过合理使用@ControllerAdvice、自定义ErrorController以及ErrorPageRegistrar,我们可以构建出健壮且用户友好的应用程序。

关键要点:

  1. 优先使用@ControllerAdvice+@ExceptionHandler:这是最灵活和推荐的方式
  2. 注意版本差异:特别是Spring Boot 2.3+和3.0+的 breaking changes
  3. 考虑异常处理优先级:合理安排异常处理器的顺序
  4. 区分API和页面请求:为不同类型的请求提供合适的响应格式
  5. 实现国际化支持:为多语言应用提供本地化的错误信息
  6. 编写测试用例:确保异常处理逻辑的正确性

通过掌握这些技巧,你能够构建出更加健壮、易维护的Spring Boot应用程序,提供更好的用户体验和更安全的错误信息处理。


欢迎在评论区分享你在Spring Boot异常处理中的经验和遇到的问题!

http://www.xdnf.cn/news/18407.html

相关文章:

  • Jenkins自动化部署服务到Kubernetes环境
  • Java 面试题训练助手 Web 版本
  • JavaScript 操作 DOM
  • php apache无法接收到Authorization header
  • express+mongoose的node部署
  • 优考试局域网系统V6.0.0版
  • AI 论文周报丨多模态记忆智能体/视觉基础模型/推理模型等多领域成果一键速览
  • AI服务器介绍
  • 《Linux 网络编程一:网络编程导论及UDP 服务器的创建与数据接收》
  • 《基于大数据的农产品交易数据分析与可视化系统》选题不当,毕业答辩可能直接挂科
  • Linux系统 --- 指令
  • tauri配置允许执行eval脚本,在打包cocos游戏web/phone移动端的时候一定要配置
  • yolo训练实例(一)
  • AAA 服务器与 RADIUS 协议笔记
  • C++函数重载与引用详解
  • Django中间件自定义开发指南:从原理到实战的深度解析
  • 【机器学习深度学习】vLLM的核心优化技术详解
  • 大型语言模型中奖励模型的原理:训练、打分与更新
  • Java面试-自动装箱与拆箱机制解析
  • 零知开源——基于ESP8266(ESP-12F)驱动YS-IR05F红外控制空调
  • pytorch 网络可视化
  • Electron 核心 API 全解析:从基础到实战场景
  • k8sday14数据存储(2/2)
  • RSS与今日头条技术对比分析
  • 代码随想录刷题Day40
  • Linux 软件包安装和管理的相关操作及使用总结(未完成)
  • 漏洞分析 | Kafka Connect 任意文件读取漏洞(CVE-2025-27817)
  • 如何使用AI大语言模型解决生活中的实际小事情?
  • 【Protues仿真】基于AT89C52单片机的LCD液晶显示屏显示控制
  • 如何在 Axios 中处理多个 baseURL 而不造成混乱