Spring全局异常处理

概述

在Spring框架中,全局异常处理主要是通过@ControllerAdvice(或其特化形式@RestControllerAdvice)注解和@ExceptionHandler注解来实现的。这种机制能够捕捉到控制器(@Controller@RestController)中抛出的异常,并允许你定义全局的处理逻辑。

依赖导入

在使用Spring Framework构建应用程序,并希望集成全局异常处理器时,你实际上并不需要添加任何特定的依赖来实现这个功能,因为它是Spring MVC提供的一个内建功能。

但确保你的项目已经包含了Spring MVC相关的依赖。对于使用Spring Boot的项目,你可以通过添加spring-boot-starter-web依赖来集成Spring MVC:

<!-- Maven配置示例 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 其他依赖... -->
</dependencies>

对于非Spring Boot项目,你可以添加相应的Spring MVC依赖,如下所示:

<!-- Maven配置示例 -->
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.x</version> <!-- 使用最新的兼容版本 -->
    </dependency>

    <!-- 其他依赖... -->
</dependencies>

这些依赖将自动引入所需的所有组件,从而使你能够使用@ControllerAdvice@RestControllerAdvice@ExceptionHandler注解来处理全局异常。

创建全局异常处理

以下是一个例子,展示了如何使用这些注解来创建一个简单的全局异常处理器:

@ControllerAdvice    // 或者使用 @RestControllerAdvice
public class GlobalExceptionHandler {

    // 捕获特定类型的异常
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<String> handleCustomException(CustomException ex) {
        // 创建响应实体并自定义相应状态码和信息
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    // 捕获所有类型的异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex) {
        // 记录异常(实际生产中你可能会记录更详细的信息)
        ex.printStackTrace(); 

        // 返回通用错误响应
        return new ResponseEntity<>("Internal server error occurred.", HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // 可以继续添加更多的异常处理方法来处理不同类型的异常
}

全局异常处理器GlobalExceptionHandler可以捕获由应用中的任何控制器抛出的异常。@ExceptionHandler注解标记的方法定义了对特定异常类型的处理逻辑。使用@ExceptionHandler(Exception.class)可以捕获并处理所有类型的异常。在这些处理方法中,可以自定义响应的HTTP状态码和返回消息。

对于REST API,通常使用@RestControllerAdvice来替代@ControllerAdvice,因为它默认地将方法的返回值封装到ResponseEntity中,并序列化为JSON或其他内容类型,而不需要在每个方法上添加@ResponseBody注解,主要用于全局异常处理,数据绑定,和数据预处理。

该注解结合了 @ControllerAdvice@ResponseBody 的特性,表明它是由 @ControllerAdvice 注解的增强版,为 RESTful 服务提供专门的异常处理。与 @ControllerAdvice 结合 @ExceptionHandler 一样,@RestControllerAdvice 也可以结合 @ExceptionHandler 使用,但是它默认为所注解的方法添加了 @ResponseBody 注解的行为。

这意味着,当你在一个以 @RestControllerAdvice 注解的类里面使用 @ExceptionHandler 注解处理异常时,不需要再显式地添加 @ResponseBody 注解。

以下是一个 @RestControllerAdvice 的简单例子,用于捕获并处理特定异常:

@RestControllerAdvice
public class GlobalRestControllerAdvice {

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<String> handleNotFoundException(NotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred: " + ex.getMessage());
    }
}

在上述代码中,GlobalRestControllerAdvice 类添加了 @RestControllerAdvice 注解,这意味着它将作用于应用中的所有 REST 控制器。里面提供了两个异常处理方法,一个用于处理 NotFoundException 类型的异常,返回 HTTP 404 状态和异常信息;而另一个用作默认的异常处理,用来处理所有其他异常并返回 HTTP 500 状态,以及一个通用错误消息。

这种全局异常处理方式简化了异常管理,允许你集中处理所有控制器中抛出的异常,提高代码的可维护性和重用性。

实际行为

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }

    /**
     *  处理SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        String message = ex.getMessage();
        if (message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username+ MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        }
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }


}

这段代码中我们在@ExceptionHandler注解中省略异常类的参数,如果这样做,那表示这个方法将处理该方法参数列表中所声明的异常类型。当注解中没有指定异常类时,它将从方法的参数类型推断。这样的用法要求方法参数必须是异常实例。

在代码例子中:

@ExceptionHandler
public Result exceptionHandler(BaseException ex) { ... }

这个exceptionHandler方法将处理所有类型为BaseException的异常,因为它作为方法的参数被声明了。

在第二个处理程序中:

@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) { ... }

这个exceptionHandler方法将处理所有类型为SQLIntegrityConstraintViolationException的异常。

这种方式会让你的异常处理器方法看起来更加简洁,而且在只处理单个异常类型的情况下非常实用。但是,如果你打算用一个方法处理多种类型的异常时,你仍然需要在注解中指定这些异常类:

@ExceptionHandler({TypeOneException.class, TypeTwoException.class})
public Result handleMultipleExceptions(Exception ex) {
    // ...
}

在这里,handleMultipleExceptions方法可以同时处理TypeOneExceptionTypeTwoException类型的异常。

最重要的是要确保异常处理程序可以处理所有的异常情况,并且返回一个合适的响应给用户。省略.class效果上和指定.class是类似的,只要方法参数类型和预期要处理的异常匹配即可。

总结

在实际的应用中,全局异常处理通常会涉及到更复杂的逻辑,如记录详细的错误日志、返回自定义错误对象、集成错误监控服务等。这些处理也都可以通过全局异常处理器来实现和集成。