Use the @RestControllerAdvice annotation in springboot to complete the elegant global exception handling class

need

The @RestControllerAdvice annotation is used in springboot to complete an elegant global exception handling class. It can first perform general processing for all exception types and then perform different processing operations for specific exception types.

Annotation explanation

@RestControllerAdvice annotation

@RestControllerAdvice is an annotation used to define a global exception handler. When an uncaught exception occurs within an application, the global exception handler will catch the exception and return the corresponding response to avoid application crashes. It can handle exceptions thrown in all controllers, including exceptions in request handling methods, exceptions in controller constructors, etc.

The @RestControllerAdvice annotation is a combination of the @ControllerAdvice and @ResponseBody annotations. Its function is to return all exception handling results to the client in JSON format.

Specifically, when an exception occurs in the controller, SpringBoot will find a handling method matching the exception in the global exception handler and execute the method to handle the exception.

The processing method can return any type of value. If the returned object is of DataVO type, it will be converted into JSON format and returned to the client.

If the return value is of type String, it is interpreted as the view name and a view parser is used to parse the view and generate an HTML response.

Therefore, using the @RestControllerAdvice annotation can conveniently define a global exception handler and return all exception handling results to the client in JSON format.

@ExceptionHandler annotation

@ExceptionHandler is an annotation used to define exception handling methods.

When an exception occurs in the controller, Spring Boot will find the method marked by the @ExceptionHandler annotation that matches the exception in the class annotated with @ControllerAdvice or @RestControllerAdvice, and execute the method to handle the exception.

The @ExceptionHandler annotation can define one or more exception types and map them to corresponding exception handling methods. When an exception of a specified type occurs in the controller, SpringBoot will automatically call the corresponding exception handling method and pass the exception object to the method as a parameter.

General global exception handling class

@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);
    }
}

As shown in the example, the GlobalExceptionHandler is declared as a global exception handling class using the @RestControllerAdvice annotation. The methods in this class use the @ExceptionHandler annotation to specify the corresponding error type handled by a certain method.

In this general global processing class, the @ExceptionHandler annotation will select the most precise processing method according to the exception type. If the corresponding processing method is not found, a more general processing method will be selected. This processing method will lead to code duplication, and each processing method needs to write common operations, such as logging.

Improve global exception handling class

In order to solve this problem, we can abstract the general exception handling logic into a method and call the method in the handling method

@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);
        }
    }
}

In this example, a private method handleException is defined that accepts a default error message and a Throwable object as parameters and returns a DataVO object.

In the handler method, call the handleException method to handle the exception and pass the default error message as a parameter to the handleException method.

In this way, we can abstract common exception handling logic into a method and avoid duplication of code. At the same time, we can also add our own logic in the handleException method, such as logging.

Elegant global exception handling class

The if-else statement in the handleException method is too bloated. You can use Map to optimize this method.

@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);
    }
}

In this example, a Map is used to store exception handling functions. Each function accepts a Throwable object as a parameter and returns a DataVO object.

In the processing method, the corresponding processing function is obtained from the Map according to the exception type. If the corresponding processing function is not found, the default processing function is used.

Then, call the obtained handler function to handle the exception. In this way, the exception handling functions can be concentrated in a Map, avoiding the bloated if-else statement. At the same time, you can also add your own logic in the exception handling function handleException, such as logging.

My general value object class 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;
    }

}

Encounter problems

During the test, I had to deal with the problem of foreign key constraint errors. The console found that the cause of the error was SQLIntegrityConstraintViolationException, so I added SQLIntegrityConstraintViolationException processing to the static exception Map, but found that it was not processed normally and the default exception information was returned.

So I output the wrong class on the console, and e.getClass() found that it was actually DataIntegrityViolationException, and SQLIntegrityConstraintViolationException was just the cause. After the change, the test successfully handled the exception and returned specific exception information to the front end.

 

Guess you like

Origin blog.csdn.net/m0_54250110/article/details/129896209