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
方法可以同时处理TypeOneException
和TypeTwoException
类型的异常。
最重要的是要确保异常处理程序可以处理所有的异常情况,并且返回一个合适的响应给用户。省略.class
效果上和指定.class
是类似的,只要方法参数类型和预期要处理的异常匹配即可。
总结
在实际的应用中,全局异常处理通常会涉及到更复杂的逻辑,如记录详细的错误日志、返回自定义错误对象、集成错误监控服务等。这些处理也都可以通过全局异常处理器来实现和集成。