【SpringBoot实用小知识】各种场景中异常的处理和捕获方式

时间:2025-01-26 13:14:55

异常的处理方式

  • 前言
  • 异常场景
    • 全局异常捕获处理
      • 使用@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相关的大小知识~