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应用的健壮性和可维护性,同时为客户端提供一致的错误响应格式。

相关推荐
不当菜虚困4 分钟前
JAVA设计模式——(九)工厂模式
java·开发语言·设计模式
暖苏5 分钟前
Spring中bean的生命周期(笔记)
java·spring boot·spring·spring cloud·mvc·bean生命周期·springbean
IT技术员9 分钟前
【Java学习】Java的CGLIB动态代理:通俗解释与使用指南
java·开发语言·学习
IvanCodes19 分钟前
Java 基础--流程控制语句
java·开发语言
caimouse27 分钟前
C#里创建一个TCP客户端连接类
java·网络·tcp/ip
Sunniering30 分钟前
Springboot整合阿里云腾讯云发送短信验证码 可随时切换短信运营商
java·阿里云·云计算·腾讯云·springboot·短信
caihuayuan531 分钟前
IOS 国际化词条 Python3 脚本
java·大数据·spring boot·后端·课程设计
wjm0410061 小时前
C++日更八股--first
java·开发语言·c++
wei3872452321 小时前
java练习2
java·开发语言·python
heyCHEEMS2 小时前
[USACO09OCT] Bessie‘s Weight Problem G Java
java·开发语言·算法