Spring Boot全局异常处理最佳实践:@RestControllerAdvice深度解析
前言
在开发Spring Boot应用时,优雅地处理异常是保证系统健壮性和用户体验的关键。本文将详细介绍如何使用@RestControllerAdvice
实现全局异常处理,并分享实际开发中的最佳实践。
一、为什么要使用全局异常处理?
-
代码复用:避免在每个Controller中重复编写try-catch
-
统一响应格式:标准化错误返回结构
-
异常分类处理:针对不同类型异常定制处理逻辑
-
减少样板代码:让业务逻辑更专注于核心流程
二、核心注解解析
1. @RestControllerAdvice
@RestControllerAdvice
是@ControllerAdvice
和@ResponseBody
的组合注解,主要功能:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@ControllerAdvice@ResponseBodypublic @interface RestControllerAdvice { // 可指定包路径或控制器类 @AliasFor(annotation = ControllerAdvice.class) String[] value() default {};}
2. @ExceptionHandler
用于标注处理特定异常的方法:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ExceptionHandler { // 指定要处理的异常类数组 Class[] value() default {};}
三、实战代码实现
以下是完整全局异常处理器实现:
他会对于ExceptionHandler指定的异常进行处理,底层是通过AOP的技术实现的。
import com.sheep.shrine.result.JSONResult;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;/** * 全局异常处理器 * @RestControllerAdvice = @ControllerAdvice + @ResponseBody */@RestControllerAdvicepublic class GlobalExceptionHandler { /** * 处理自定义业务异常 */ @ExceptionHandler(GlobleException.class) public JSONResult handleBusinessException(GlobleException e) { e.printStackTrace(); return JSONResult.error(e.getMessage()); } /** * 处理所有未捕获异常 */ @ExceptionHandler(Exception.class) public JSONResult handleSystemException(Exception e) { e.printStackTrace(); return JSONResult.error(\"系统异常,正在殴打程序员...\", \"50000\"); }}
四、进阶使用技巧
1. 异常分类处理
// 处理数据校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public JSONResult handleValidException(MethodArgumentNotValidException e) { String message = e.getBindingResult().getAllErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining(\"; \")); return JSONResult.error(message);}// 处理数据库异常@ExceptionHandler(DataAccessException.class)public JSONResult handleDataAccessException(DataAccessException e) { log.error(\"数据库操作异常\", e); return JSONResult.error(\"数据库服务异常\");}
2. 响应状态码控制
@ExceptionHandler(UnauthorizedException.class)@ResponseStatus(HttpStatus.FORBIDDEN)public JSONResult handleUnauthorizedException(UnauthorizedException e) { return JSONResult.error(\"无权限访问\", \"403\");}
3. 日志记录优化
@Slf4j@RestControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public JSONResult handleException(Exception e) { log.error(\"系统异常: {}\", e.getMessage(), e); return JSONResult.error(\"系统繁忙\"); }}
五、最佳实践建议
-
异常分类细化:不要只用一个Exception.class处理所有异常
-
敏感信息过滤:生产环境不要返回堆栈信息
-
错误码规范:制定统一的错误码体系
-
日志完善:关键异常必须记录完整上下文
-
性能考虑:异常处理逻辑应尽量轻量
六、常见问题解答
Q1:全局异常处理器不生效怎么办?
-
检查是否在Spring扫描路径下
-
确认没有其他异常处理器覆盖
-
检查是否有Filter提前处理了异常
Q2:如何测试全局异常处理器?
@SpringBootTestclass GlobalExceptionHandlerTest { @Autowired private WebApplicationContext context; private MockMvc mockMvc; @BeforeEach void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Test void testBusinessException() throws Exception { mockMvc.perform(get(\"/api/test-exception\")) .andExpect(status().isOk()) .andExpect(jsonPath(\"$.code\").value(\"50000\")); }}
Q3:如何与FeignClient集成?
@Configurationpublic class FeignConfig { @Bean public ErrorDecoder errorDecoder() { return (methodKey, response) -> { // 将Feign异常转换为自定义异常 return new BusinessException(\"远程服务调用失败\"); }; }}
七、总结
通过@RestControllerAdvice
实现全局异常处理可以:
-
提高代码可维护性
-
增强系统健壮性
-
改善用户体验
-
便于监控报警