JAVA中Spring全局异常处理@ControllerAdvice解析

一、@ControllerAdvice基础概念

1. 什么是@ControllerAdvice?

@ControllerAdvice是Spring 3.2引入的注解,用于定义全局控制器增强组件,主要功能包括:

  • 全局异常处理(最常用)
  • 全局数据绑定
  • 全局数据预处理

2. 核心作用

  • 集中处理控制器层异常
  • 避免重复的异常处理代码
  • 统一API错误响应格式
  • 减少try-catch块污染业务代码

二、基础全局异常处理实现

1. 最小实现示例

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            "服务器内部错误",
            ex.getMessage()
        );
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// 统一错误响应DTO
@Data
@AllArgsConstructor
class ErrorResponse {
    private int status;
    private String error;
    private String message;
    private long timestamp = System.currentTimeMillis();
}

2. 处理特定异常

java 复制代码
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
    ResourceNotFoundException ex) {
    
    ErrorResponse error = new ErrorResponse(
        HttpStatus.NOT_FOUND.value(),
        "资源未找到",
        ex.getMessage()
    );
    return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}

三、高级配置技巧

1. 限定控制器范围

java 复制代码
// 只处理指定包下的控制器
@ControllerAdvice(basePackages = "com.example.web.controllers")

// 只处理带有特定注解的控制器
@ControllerAdvice(annotations = RestController.class)

// 只处理指定类
@ControllerAdvice(assignableTypes = {UserController.class, ProductController.class})

2. 异常处理优先级

Spring会按照最具体到最通用的顺序匹配@ExceptionHandler:

java 复制代码
@ExceptionHandler(FileUploadException.class)  // 优先匹配
public ResponseEntity<?> handleFileUpload(FileUploadException ex) { ... }

@ExceptionHandler(IOException.class)          // 次级匹配
public ResponseEntity<?> handleIO(IOException ex) { ... }

@ExceptionHandler(Exception.class)            // 兜底处理
public ResponseEntity<?> handleGeneral(Exception ex) { ... }

3. 获取请求上下文

通过注入请求对象获取更多信息:

java 复制代码
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(
    Exception ex, 
    WebRequest request) {
    
    String path = ((ServletWebRequest)request).getRequest().getRequestURI();
    
    ErrorResponse error = new ErrorResponse(
        HttpStatus.INTERNAL_SERVER_ERROR.value(),
        "处理请求[" + path + "]时发生错误",
        ex.getMessage()
    );
    return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}

四、常见异常处理模式

1. 业务异常处理

java 复制代码
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(
    BusinessException ex) {
    
    ErrorResponse error = new ErrorResponse(
        ex.getErrorCode(),
        ex.getErrorType(),
        ex.getMessage()
    );
    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

2. 数据校验异常

java 复制代码
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
    MethodArgumentNotValidException ex) {
    
    List<String> errors = ex.getBindingResult()
        .getFieldErrors()
        .stream()
        .map(FieldError::getDefaultMessage)
        .collect(Collectors.toList());
    
    ErrorResponse error = new ErrorResponse(
        HttpStatus.BAD_REQUEST.value(),
        "参数校验失败",
        errors.toString()
    );
    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

3. 认证授权异常

java 复制代码
@ExceptionHandler({
    AccessDeniedException.class,
    AuthenticationException.class
})
public ResponseEntity<ErrorResponse> handleAuthException(
    RuntimeException ex) {
    
    HttpStatus status = ex instanceof AccessDeniedException 
        ? HttpStatus.FORBIDDEN 
        : HttpStatus.UNAUTHORIZED;
    
    ErrorResponse error = new ErrorResponse(
        status.value(),
        "权限不足",
        ex.getMessage()
    );
    return new ResponseEntity<>(error, status);
}

五、最佳实践建议

1. 异常分类处理

建议将异常分为几大类分别处理:

异常类型 处理方式 HTTP状态码
业务异常 返回具体错误信息 400-499
系统异常 记录日志,返回通用错误 500
第三方服务异常 记录日志,返回服务不可用 503
参数校验异常 返回详细校验错误 400

2. 日志记录策略

java 复制代码
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(
    Exception ex, 
    HttpServletRequest request) {
    
    log.error("请求[{} {}]处理失败", 
        request.getMethod(), 
        request.getRequestURI(), 
        ex);
    
    // ...返回错误响应
}

3. 生产环境与开发环境差异

java 复制代码
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(
    Exception ex,
    Environment env) {
    
    boolean isProd = Arrays.asList(env.getActiveProfiles())
        .contains("prod");
    
    String message = isProd 
        ? "服务器错误,请联系管理员" 
        : ex.getMessage();
    
    // ...返回错误响应
}

六、原理深度解析

1. 实现原理

@ControllerAdvice的工作原理:

  1. 通过@ControllerAdvice标记的类会被ExceptionHandlerExceptionResolver识别
  2. Spring初始化时会收集所有@ExceptionHandler方法
  3. 当控制器抛出异常时,DispatcherServlet会:
    • 查找当前控制器内的@ExceptionHandler方法
    • 如果没有,则查找@ControllerAdvice中的处理方法
    • 按异常类型匹配最具体的方法

2. 核心组件协作

复制代码
[DispatcherServlet]
    |
    v
[HandlerExceptionResolver]
    |
    |-- [ExceptionHandlerExceptionResolver] (处理@ExceptionHandler)
    |-- [ResponseStatusExceptionResolver] (处理@ResponseStatus)
    `-- [DefaultHandlerExceptionResolver] (处理Spring标准异常)

3. 执行顺序

  1. @ExceptionHandler方法(当前控制器内)
  2. @ControllerAdvice中的@ExceptionHandler方法
  3. @ResponseStatus异常
  4. Spring默认异常处理

七、常见问题解决方案

1. 异常处理不生效

可能原因:

  • 未启用注解驱动:确保配置了@EnableWebMvc或<mvc:annotation-driven/>
  • 包扫描问题:@ControllerAdvice类未被Spring管理
  • 异常被捕获:上游代码已经catch了异常

2. 处理多个相同类型异常

java 复制代码
@ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class})
public ResponseEntity<?> handleIllegal(RuntimeException ex) {
    // 统一处理多种相似异常
}

3. 自定义异常解析器

如需更复杂控制,可以实现HandlerExceptionResolver:

java 复制代码
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler,
        Exception ex) {
        
        // 自定义异常处理逻辑
        return ...;
    }
}

八、Spring Boot增强支持

1. 错误属性配置

application.properties:

properties 复制代码
server.error.include-message=always
server.error.include-stacktrace=on_param
server.error.include-binding-errors=always

2. 自定义ErrorController

java 复制代码
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CustomErrorController implements ErrorController {

    @RequestMapping
    public ResponseEntity<ErrorResponse> handleError(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        ErrorResponse error = new ErrorResponse(
            status.value(),
            "自定义错误处理",
            (String)request.getAttribute("javax.servlet.error.message")
        );
        return new ResponseEntity<>(error, status);
    }
    
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer code = (Integer) request.getAttribute("javax.servlet.error.status_code");
        return code != null ? HttpStatus.valueOf(code) : HttpStatus.INTERNAL_SERVER_ERROR;
    }
}

九、性能优化建议

  1. 减少@ControllerAdvice扫描范围:精确指定basePackages
  2. 避免在异常处理中进行IO操作:如数据库写入
  3. 使用响应缓存:对相同异常返回缓存响应
  4. 异步异常处理 :对耗时处理使用@Async
java 复制代码
@ExceptionHandler(ReportableException.class)
@Async
public CompletableFuture<ResponseEntity<?>> handleReportable(ReportableException ex) {
    // 异步处理可报告异常
    reportService.sendReport(ex);
    return CompletableFuture.completedFuture(ResponseEntity.badRequest().build());
}

通过合理使用@ControllerAdvice进行全局异常处理,可以显著提高Spring应用的健壮性和可维护性,同时为客户端提供一致的错误响应格式。

相关推荐
这个昵称不容易3 小时前
SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在
java·springboot·毕设
计算机毕设定制辅导-无忧学长4 小时前
《基于微信小程序的居家医疗陪护预约系统设计与实现》
java·spring boot·微信小程序·uniapp·居家医疗陪护预约系统
文丑颜不良啊5 小时前
LeetCode2221~2240题目解析
java·leetcode
秋饼5 小时前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发
文丑颜不良啊7 小时前
LeetCode2161~2180题目解析
java·leetcode
文丑颜不良啊9 小时前
LeetCode2141~2160题目解析
java·leetcode
LabVIEW开发9 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
天天被压力10 小时前
【A股分钟级高频数据完全手册 #07】北交所历史分钟K线接口:京市Pro版分时批量
java·人工智能·python
天天被压力10 小时前
【A股分钟级高频数据完全手册 #08】股票日、分时数据接口对比:普通分时与Pro版差异实测
java·人工智能·python
特立独行的猫A10 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python