异常的处理方式
- 前言
- 异常场景
- 全局异常捕获处理
- 使用@ControllerAdvice和@ExceptionHandler
- 使用@RestControllerAdvice
- 自定义错误响应
- 使用@ResponseStatus
- 使用ApplicationListener
- AOP中异常捕获处理
- 内部消化的场景(不需要返回给前端)
- 响应的场景(需要返回给前端)
- 事务上下文的异常捕获处理
- 最后
前言
无论是在什么项目中 异常都是一个避不开的话题 那么这里列出在编写项目中 比较常见的异常场景 以及它们捕获和处理的方式 并给出处理方式的适用场景 我们将从控制器层 AOP 基础层(数据库交互层)可能出现的异常场景来写
异常场景
全局异常捕获处理
首先是我们很熟悉的全局异常的捕获处理 这里有很多种方案 我们将依次列举
使用@ControllerAdvice和@ExceptionHandler
很简单也很常见 适用场景:当需要在一个集中点处理所有控制器中发生的异常时,可以使用@ControllerAdvice它可以用于统一异常处理逻辑,如格式化错误消息、设置HTTP状态码等
你可以控制它们处理异常的顺序 使用**@Order**注解 值越小优先级越大
@ControllerAdvice
public class GlobalExceptionHandler {
//设置捕获的异常以及处理逻辑
@ExceptionHandler(value = IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
//前端将接收这样的响应
return ResponseEntity.status(400).body("Invalid argument: " + ex.getMessage());
}
@ExceptionHandler(value = Exception.class)
public ResponseEntity<String> handleAllExceptions(Exception ex) {
return ResponseEntity.status(500).body("An unexpected error occurred: " + ex.getMessage());
}
}
使用@RestControllerAdvice
适用场景:与@ControllerAdvice类似,但是更加专注于@RestController控制器。如果项目主要基于RESTful API 那么使用@RestControllerAdvice可以减少不必要的配置 因为它只针对@RestController
减少的配置也是基于此 具体如下
1.自动检测@RestController控制器:
@RestControllerAdvice默认只对带有@RestController注解的控制器生效。这意味着你无需在每个@ControllerAdvice类上显式地添加@ResponseBody注解来确保响应体能够正确地序列化成JSON或XML等格式。@RestControllerAdvice自动包含了@ResponseBody的功能,因此减少了代码量。
2.避免冲突:
当你在一个包含@Controller和@RestController的混合应用中使用@ControllerAdvice时,需要小心地管理哪些异常处理器应该应用于哪种类型的控制器。@RestControllerAdvice消除了这种混淆,因为它只应用于@RestController,避免了在处理异常时的意外冲突。
3.简化配置:
由于@RestControllerAdvice的专一性 它不需要额外的配置来指明它应该作用于哪一类控制器 这使得异常处理的配置更加简单和直接 减少了配置上的工作量
4.明确的意图表达:
使用@RestControllerAdvice明确表明了该异常处理器是专门为@RestController设计的 提高了代码的可读性和可维护性
如果是@RestController抛出的异常 除非@RestControllerAdvice没有捕获此异常 否则@ControllerAdvice不会插手异常的处理 但是有**@Order**则注解的设置的优先级更大
@RestControllerAdvice
public class GlobalRestControllerExceptionHandler {
@ExceptionHandler(value = IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body("Invalid argument: " + ex.getMessage());
}
}
自定义错误响应
适用场景:当你需要返回结构化的错误响应给客户端 比如JSON格式的错误信息 这时可以使用自定义错误响应类 它使错误响应更加可读 便于客户端解析
public class ErrorResponse {
//状态码
private int status;
//信息
private String message;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
// Getters and setters
}
结合全局处理器来完成
@ControllerAdvice
public class CustomGlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex) {
ErrorResponse error = new ErrorResponse(500, "An unexpected error occurred: " + ex.getMessage());
return ResponseEntity.status(500).body(error);
}
}
使用@ResponseStatus
适用场景:当某个特定的异常应该总是对应特定的HTTP状态码时 可以使用@ResponseStatus 这通常用于自定义异常类 确保每次抛出此类异常时都会返回预设的状态码
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
使用ApplicationListener
适用场景:当需要监听和处理异步请求中的错误事件时 可以使用ApplicationListener 它可以在异步请求完成时捕获异常 如可以进行异步处理逻辑的错误监控和日志记录
public class GlobalErrorListener implements ApplicationListener<ErrorEvent> {
@Override
public void onApplicationEvent(ErrorEvent event) {
Throwable error = event.getError();
WebRequest request = event.getWebRequest();
// 这里可以添加日志记录或发送错误报告
System.out.println("An error occurred in asynchronous request: " + error.getMessage());
}
}
注意 这里的ErrorEvent需要继承ApplicationEvent类
AOP中异常捕获处理
内部消化的场景(不需要返回给前端)
我们可以使用AOP的切面 对异常进行捕获 可能说起来很抽象 但是给出代码就很简单了
@Aspect
@Component
public class ExceptionHandlingAspect {
// 定义一个切入点,匹配所有service层的方法
@Pointcut("execution(* .*.*(..))")
public void serviceLayerExecution() {
// 自定义逻辑
}
// 当切入点抛出异常时,执行此方法
@AfterThrowing(pointcut = "serviceLayerExecution()", throwing = "ex")
public void handleServiceException(Exception ex) {
// 处理异常,例如记录日志、发送错误通知等
System.out.println("Exception occurred: " + ex.getMessage());
}
}
这样操作 我们将AOP的切面方法和原始方法抛出的异常都交给了被**@AfterThrowing** 注解标注的方法处理 如果想要自定义异常 可以在方法参数中更改类型(类型要派生自Throwable) 但是参数名要和throwing的值一致
响应的场景(需要返回给前端)
如果不想内部消化AOP抛出的异常 而是返回给前端一些信息 可以结合上文中的全局异常处理器 + 自定义异常完成这样一个操作
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
//设置异常类型
@ExceptionHandler(PermissionException.class)
//设置状态码
@ResponseStatus(HttpStatus.FORBIDDEN)
public ResponseEntity<Result> handlePermissionException(PermissionException exception){
//处理
return new ResponseEntity<>(Result.error(exception.getMsg()),HttpStatus.FORBIDDEN);
}
}
事务上下文的异常捕获处理
很多业务中 我们对于多表操作 都会加上事务注解 如果想要让事务上下文在指定的异常回退 那么我们可以怎样在回退的情况下还能知道发生了什么异常呢?我们可以捕获之后再次抛出我们想要的自定义异常
@Transactional(rollbackFor = CustomException.class)
public void someOperation() {
try {
// 执行业务逻辑
if (/* 某些条件 */) {
throw new CustomException(ErrorCode.SOME_ERROR, "发生了错误");
}
} catch (Exception e) {
// 可以在这里记录异常信息,例如写入日志
log.error("An error occurred during operation", e);
throw new CustomException(ErrorCode.SOME_ERROR, e.getMessage());
}
}
这样的方式 我们可以在调用此方法的上层捕获到这个CustomException并进行一些逻辑处理
最后
这是博主在写项目时 常见的异常捕获和处理场景 欢迎各位读者的讨论以及关注 我还会带来更多的Java相关的大小知识~