springboot中使用@RestControllerAdvice注解,完成优雅的全局异常处理类

需求

springboot中使用@RestControllerAdvice注解,完成优雅的全局异常处理类,可以针对所有异常类型先进行通用处理后再对特定异常类型进行不同的处理操作。

注解讲解

@RestControllerAdvice注解

@RestControllerAdvice是一个用于定义全局异常处理器的注解。当应用程序内发生未捕获的异常时,全局异常处理器将捕获该异常并返回对应的响应,以避免应用程序崩溃。它可以处理所有控制器中抛出的异常,包括请求处理方法中的异常、控制器构造函数中的异常等。

@RestControllerAdvice注解是@ControllerAdvice和@ResponseBody注解的组合,它的作用是将所有的异常处理结果都以JSON格式返回给客户端。

具体来说,当控制器中发生异常时,SpringBoot会在全局异常处理器中查找与异常匹配的处理方法,并执行该方法来处理异常。

处理方法可以返回任何类型的值,如果返回对象是DataVO类型,则会将其转换为JSON格式并返回给客户端。

如果返回值是String类型,则会将其解释为视图名称,并使用视图解析器来解析视图并生成HTML响应。

因此,使用@RestControllerAdvice注解可以方便地定义全局异常处理器,并将所有异常处理结果以JSON格式返回给客户端。

@ExceptionHandler注解

@ExceptionHandler是一个注解,用于定义异常处理方法。

当控制器中发生异常时,Spring Boot会在@ControllerAdvice或@RestControllerAdvice注解的类中查找与异常匹配的@ExceptionHandler注解标记的方法,并执行该方法来处理异常。

@ExceptionHandler注解可以定义一个或多个异常类型,并将它们映射到对应的异常处理方法。当控制器中发生指定类型的异常时,SpringBoot会自动调用对应的异常处理方法,并将异常对象传递给该方法作为参数。

一般的全局异常处理类

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final String DEFAULT_ERROR_MESSAGE = "系统繁忙,请稍后再试";

    @ExceptionHandler(value = {SQLIntegrityConstraintViolationException.class})
    public DataVO handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
        log.error(e.getMessage(), e);
        return new DataVO(SysConstant.CODE_ERROR, "当前主键存在于其他表的外键约束,请先处理父表中的该外键");
    }

    @ExceptionHandler(value = {FileNotFoundException.class})
    public DataVO handleFileNotFoundException(FileNotFoundException e) {
        log.error(e.getMessage(), e);
        return new DataVO(SysConstant.CODE_ERROR, "路径不存在");
    }

    @ExceptionHandler(value = {Exception.class})
    public DataVO handleException(Exception e) {
        log.error(e.getMessage(), e);
        return new DataVO(SysConstant.CODE_ERROR, DEFAULT_ERROR_MESSAGE);
    }
}

如例所示,使用@RestControllerAdvice注解声明了GlobalExceptionHandler作为全局异常处理类,在这个类中的方法使用@ExceptionHandler注解指定某个方法处理对应的错误类型。

在这个一般的全局处理类中,@ExceptionHandler注解会根据异常类型选择最精确的处理方法进行处理,如果没有找到对应的处理方法,则会选择更加通用的处理方法。这样的处理方式会导致代码重复,每个处理方法都需要写一遍通用的操作,例如日志记录。

改进全局异常处理类

为了解决这个问题,我们可以将通用的异常处理逻辑抽象到一个方法中,并在处理方法中调用该方法

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final String DEFAULT_ERROR_MESSAGE = "系统繁忙,请稍后再试";

    @ExceptionHandler(value = {SQLIntegrityConstraintViolationException.class})
    public DataVO handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
        log.error(e.getMessage(), e);
        return handleException(DEFAULT_ERROR_MESSAGE, e);
    }

    @ExceptionHandler(value = {FileNotFoundException.class})
    public DataVO handleFileNotFoundException(FileNotFoundException e) {
        log.error(e.getMessage(), e);
        return handleException(DEFAULT_ERROR_MESSAGE, e);
    }

    @ExceptionHandler(value = {Exception.class})
    public DataVO handleException(Exception e) {
        log.error(e.getMessage(), e);
        return handleException(DEFAULT_ERROR_MESSAGE, e);
    }

    private DataVO handleException(String defaultMessage, Throwable e) {
        if (e instanceof BusinessException) {
            return new DataVO(SysConstant.CODE_ERROR, e.getMessage());
        } else if (e instanceof MethodArgumentNotValidException) {
            return new DataVO(SysConstant.CODE_ERROR, ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage());
        } else {
            return new DataVO(SysConstant.CODE_ERROR, defaultMessage);
        }
    }
}

在这个示例中,定义了一个私有方法handleException,该方法接受一个默认错误消息和一个Throwable对象作为参数,并返回一个DataVO对象。

在处理方法中,调用handleException方法来处理异常,并将默认错误消息作为参数传递给handleException方法。

这样,我们就可以将通用的异常处理逻辑抽象到一个方法中,避免了重复代码。同时,我们也可以在handleException方法中添加自己的逻辑,例如记录日志等。

优雅的全局异常处理类

handleException方法中的if-else语句太过臃肿,可以使用Map来优化这个方法

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final String errorMsg = SysConstant.DEFAULT_ERROR;
    private static final Map<Class<? extends Throwable>, Function<Throwable, DataVO<Null>>> EXCEPTION_HANDLER_MAP = new HashMap<>();

    static {
        EXCEPTION_HANDLER_MAP.put(RuntimeException.class, e -> new DataVO<>(SysConstant.CODE_ERROR, e.getMessage()));
        EXCEPTION_HANDLER_MAP.put(DataIntegrityViolationException.class, e -> new DataVO<>(SysConstant.CODE_ERROR, "当前主键存在于其他表的外键约束,请先处理父表中的该外键"));
        EXCEPTION_HANDLER_MAP.put(FileNotFoundException.class, e -> new DataVO<>(SysConstant.CODE_ERROR, "路径不存在"));
    }

    @ExceptionHandler(Exception.class)
    public DataVO<Null> handleException(Exception e) {
        log.error(e.toString());
        return EXCEPTION_HANDLER_MAP.getOrDefault(e.getClass(), t -> new DataVO<>(SysConstant.CODE_ERROR, errorMsg)).apply(e);
    }
}

在这个示例中,使用Map来存储异常处理函数,每个函数都接受一个Throwable对象作为参数,并返回一个DataVO对象。

在处理方法中,根据异常类型从Map中获取对应的处理函数,如果没有找到对应的处理函数,则使用默认的处理函数。

然后,调用获取到的处理函数来处理异常。这样,就可以将异常处理函数集中在一个Map中,避免了if-else语句的臃肿。同时,也可以在异常处理函数handleException中添加自己的逻辑,例如记录日志等。

我的通用值对象类DataVO(Data Value Object)

@Data
//@JsonPropertyOrder({"code","msg","count","data"})//指定返回给前端的字段顺序
public class DataVO<T> {
    private Integer code = 0;
    private String msg = "";
    private Long count;
    private List<T> data;

    public DataVO() {
    }

    public DataVO(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public DataVO(Integer code, String msg, Long count, List<T> data) {
        this.code = code;
        this.msg = msg;
        this.count = count;
        this.data = data;
    }

}

遇到问题

在测试的时候要处理外键约束报错问题,控制台发现报错原因是SQLIntegrityConstraintViolationException,于是我在静态异常Map中加入了SQLIntegrityConstraintViolationException处理,但是发现并没有正常处理,还是返回的默认异常信息。

于是在控制台输出了一下那个错误的类,e.getClass()发现实际上是DataIntegrityViolationException,SQLIntegrityConstraintViolationException只是cause,改完之后测试成功处理异常并返回特定异常信息给前端。

猜你喜欢

转载自blog.csdn.net/m0_54250110/article/details/129896209
今日推荐