SpringBoot全局异常处理

当客户端/前端向服务端发送一个请求后,这个请求并不是每次都能完全正确的处理,比如出现一些资源不存在、参数错误或者内部错误等信息的时候,就需要将异常反馈给客户端或者前端。那么这就需要程序有完整的异常处理机制。

在 Java 中所有异常的基类都是 Throwable 延伸出来,但通常不使用 Throwable,在该基类延伸出来 Error 和 Exception 两种异常,严格意义上讲 Error 不属于异常,而是错误,Exception 才是异常,两者有什么区别呢?所谓错误就是应用发生错误的是就启动不起来,一般都是操作系统或者是 JVM 级别上发生的错误,一旦发生错误,我们是无法通过代码来进行处理的;但是 Exception 一般是我们可以通过代码来进行处理的,例如我们平时查询数据库没有找到某一条记录,就是一个典型的空异常,再比如我们进行计算的时候,分母为 0, 也会报一个异常。

Error 在平常工作我们是不需要处理,重点就是 Exception,Exception 又分为两种,一种是 CheckedException 异常,另一种是 RuntimeException 异常;这两个异常的区别是 CheckedException 要求必须在代码中进行处理,如果不处理,程序连编译都无法通过;RuntimeException不要求强制进行处理,因为这个异常属于运行的异常,不一定在编译阶段就能发现。如下面示例代码:

Exception:

SpringBoot全局异常处理_第1张图片

这里 throw new Exception(), 要么进行 try...catch, 要么使用 throws Exception 抛出去,否则就会提示报错,程序无法编译通过。

下图示例,RuntimeException,即使不做任何处理也不会报错,是可以正常编译的

SpringBoot全局异常处理_第2张图片

RuntimeException 是一个继承了 Exception 异常类的具体类,CheckException 是没有这个类的,例如我们自定义一个 HttpException extends RuntimeException,那么这个HttpException 就是一个运行时异常,如果一个 APIException extends Exception,那么就是一个 CheckException 异常。

介绍完了异常的基础知识后,我们如何进行统一异常处理?

1、定义统一返给前端的格式

统一返回给前端的格式是一个 JSON 类型,包含有自定义的 code 码、异常信息以及请求信息

@Getter
@Setter
public class UnifyResponse {
    private Integer code;
    private String message;
    private String request;

    public UnifyResponse(Integer code, String message, String request) {
        this.code = code;
        this.message = message;
        this.request = request;
    }
}

2、自定义一个 HttpException 类,用于处理已知异常

@Getter
public class HttpException extends RuntimeException {
    protected Integer code;
    protected Integer httpStatusCode = 500;
}

这里可以根据 HttpStatus 进行定义出来具体的异常类,例如 404 为资源找不到,401 为无权限等

public class NotFoundException extends HttpException {
    public NotFoundException(int code) {
        this.code = code;
        this.httpStatusCode = 404;
    }
}
public class ForbiddenException extends HttpException {
    public ForbiddenException(int code) {
        this.code = code;
        this.httpStatusCode = 401;
    }
}

需要注意的是在 HttpException 中只声明了连个变量,code 和 httpStatus,并没有 message(错误信息),是因为这里我们将错误信息封装到了一个配置文件中,然后通过类读取配置文件,根据 code 码来获取到对应的 message

3、resources下创建一个config/exception-code.properties

gabriel.codes[10000]=通用异常
gabriel.codes[10001]=通用参数错误

4、创建类获取配置文件信息

package com.gx.missyou.core.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 该类主要用于根据 code 值获取异常的 message 消息
 * SpringBoot 中通过 @PropertySource 注解将类与配置文件进行关联
 * 用法:@PropertySource(classpath:config/exception-code.properties)
 * 

* 如何将该类中的私有字段 codes 与配置文件中所有的配置相关联? * 1. 配置文件中的 gabriel.codes["xxxxx"] 这里的 codes 会和私有变量 Map codes * 中的 codes 自动关联 * 2. 通过 @ConfigurationProperties(prefix="gabriel") 进行配置前缀,这里的 gabriel 必须要和配置 * 文件中的 gabriel.codes[10000] = "通用异常" 前缀 gabriel 保持一致 *

* 注意:SpringBoot 自动将配置文件和类进行关联的时候,类是必须要在容器中才可以的,所以在类上必须要添加 * 一个 @Component 注解 */ @PropertySource(value = "classpath:/config/exception-code.properties") @ConfigurationProperties(prefix = "gabriel") @Component public class ExceptionCodeConfiguration { private Map codes = new HashMap<>(); public Map getCodes() { return codes; } public void setCodes(Map codes) { this.codes = codes; } public String getMessages(int code) { return codes.get(code); } }

5、完善统一捕获异常文件

@ControllerAdvice
public class GlobalExceptionAdvice {

    @Autowired
    private ExceptionCodeConfiguration codeConfiguration;

    // 处理未知异常
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public UnifyResponse handleException(HttpServletRequest request, Exception e) {
        System.out.println("this is handleException");
        String uri = request.getRequestURI();
        String method = request.getMethod();
        System.out.println("异常原因:" + e);

        return new UnifyResponse(9999, "服务器内部异常", method + " " + uri);
    }

    // 处理已知异常(自定义异常)
    @ExceptionHandler(HttpException.class)
    public ResponseEntity handleHttpException(HttpServletRequest request, HttpException e) {
        String requestUrl = request.getRequestURI();
        String method = request.getMethod();

        String messages = codeConfiguration.getMessages(e.getCode());

        // 组装 ResponseEntity
        UnifyResponse message = new UnifyResponse(e.getCode(), messages, method + " " + requestUrl);
        // 设置 HttpHeaders
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        // 设置 HttpStatus
        HttpStatus httpStatus = HttpStatus.resolve(e.getHttpStatusCode());

        assert httpStatus != null;
        return new ResponseEntity<>(message, headers, httpStatus);
    }
}

 

添加由于参数校验后返回给前台的的异常信息

 @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public UnifyResponse handleBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
        String requestUrl = request.getRequestURI();
        String method = request.getMethod();

        List allErrors = e.getBindingResult().getAllErrors();
        String message = this.formatAllErrorMessages(allErrors);

        return new UnifyResponse(10001, message, method + " " + requestUrl);
    }

  private String formatAllErrorMessages(List errors) {
        StringBuffer errorMsg = new StringBuffer();
        errors.forEach(objectError -> errorMsg.append(objectError.getDefaultMessage()).append(";"));

        return errorMsg.toString();
    }

添加处理URL和查询参数的异常

   @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public UnifyResponse handleConstraintException(HttpServletRequest request, ConstraintViolationException e) {
        String uri = request.getRequestURI();
        String method = request.getMethod();

        // 可以直接通过 e.getMessage() 获取到所有的异常信息,也可以通过自定义循环去获取
        // String message = e.getMessage();

        StringBuilder messages = new StringBuilder();
        // 通过循环自定义去获取 message
        for (ConstraintViolation error : e.getConstraintViolations()) {
            messages.append(error.getMessage()).append(";");
        }

        return new UnifyResponse(10001, messages.toString(), method + " " + uri);
    }

 

你可能感兴趣的:(SpringBoot全局异常处理)