文章开头.jpg

为什么使用全局异常处理?

在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。

有的人会问如果发生异常为什么我们需要进行处理?因为优雅的返回格式会让后期维护以及功能实现上有一个很大的帮助,比如如果请求方式不对,原本的接口访问请求方式是post但是前端使用的是get请求,如果我们{code:”1000”,msg:”请求方式不对”,data:{}}这样处理后的异常返回就很友好,如果直接返回异常信息难免会很不友好,所以全局异常处理是必要的也是整个项目必须的.

SpringBoot全局异常

(1) .全局响应处理

①ResultVo类 ——->作用固定返回格式

  1. /**
  2. * @author 王振宇
  3. * Date: Created in 2021-04-06
  4. * Utils: Intellij Idea
  5. * Description: 固定返回格式
  6. */
  7. @Data
  8. @ApiModel("固定返回格式")
  9. public class ResultVo {
  10. /**
  11. * 错误码
  12. */
  13. @ApiModelProperty("错误码")
  14. private Integer code;
  15. /**
  16. * 提示信息
  17. */
  18. @ApiModelProperty("提示信息")
  19. private String message;
  20. /**
  21. * 具体的内容
  22. */
  23. @ApiModelProperty("响应数据")
  24. private Object data;
  25. }

②ResultEnum类———>返回状态枚举类

/**
 * @author 王振与
 * Date: Created in 2021-04-06
 * Utils: Intellij Idea
 * Description: 返回状态枚举类
 */
@Getter
public enum ResultEnum {
    /**
     * 未知异常
     */
    UNKNOWN_EXCEPTION(100, "未知异常"),

    /**
     * 格式错误
     */
    FORMAT_ERROR(101, "参数格式错误"),

    /**
     * 超时
     */
    TIME_OUT(102, "超时"),
    ;
    /**
     * 参数类型不匹配
     */
    ARGUMENT_TYPE_MISMATCH(107, "参数类型不匹配"),

    /**
     * 请求方式不支持
     */
    REQ_METHOD_NOT_SUPPORT(110,"请求方式不支持"),
    ;

    private Integer code;

    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    /**
     * 通过状态码获取枚举对象
     * @param code 状态码
     * @return 枚举对象
     */
    public static ResultEnum getByCode(int code){
        for (ResultEnum resultEnum : ResultEnum.values()) {
            if(code == resultEnum.getCode()){
                return resultEnum;
            }
        }
        return null;
    }
}

③ResultVoUtil类———>返回数据工具类

/**
 * @author 王振宇
 * Date: Created in 18/8/20 上午11:05
 * Utils: Intellij Idea
 * Description: 返回数据工具类
 */
public class ResultVoUtil {

    /**
     * 私有化工具类 防止被实例化
     * j
     */
    private ResultVoUtil() {}

    /**
     * 成功
     * @param object 需要返回的数据
     * @return data
     */
    public static ResultVo success(Object object) {
        ResultVo result = new ResultVo();
        result.setCode(200);
        result.setMessage("ok");
        result.setData(object);
        return result;
    }

    /**
     * 成功
     * @return 返回空
     */
    public static ResultVo success() {
        return success(null);
    }

    /**
     * 错误
     * @param resultEnum 错误枚举类
     * @return 错误信息
     */
    public static ResultVo error(ResultEnum resultEnum) {
        ResultVo result = new ResultVo();
        result.setCode(resultEnum.getCode());
        result.setMessage(resultEnum.getMsg());
        return result;
    }

    /**
     * 错误
     * @param code 状态码
     * @param msg 消息
     * @return ResultBean
     */
    public static ResultVo error(Integer code, String msg) {
        ResultVo result = new ResultVo();
        result.setCode(code);
        result.setMessage(msg);
        return result;
    }

    /**
     * 错误
     * @param msg 错误信息
     * @return ResultBean
     */
    public static ResultVo error(String msg) {
        return error(-1, msg);
    }

}

(2). 自定义异常类

/**
 * @author 王振宇
 * Date: Created in 2020-04-06
 * Utils: Intellij Idea
 * Description: 自定义异常
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class CustomException extends RuntimeException {

    /**
     * 状态码
     */
    private final Integer code;

    /**
     * 方法名称
     */
    private final String method;


    /**
     * 自定义异常
     *
     * @param resultEnum 返回枚举对象
     * @param method     方法
     */
    public CustomException(ResultEnum resultEnum, String method) {
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
        this.method = method;
    }

    /**
     * @param code    状态码
     * @param message 错误信息
     * @param method  方法
     */
    public CustomException(Integer code, String message, String method) {
        super(message);
        this.code = code;
        this.method = method;
    }

}

CustomException类 继承于RuntimeException(运行时异常)

(3) .全局异常处理

跟定义的全局响应处理以及自定义异常处理一块使用
GlobalExceptionHandling类

/**
 * @author 王振宇
 * Date: Created in 2021-04-06
 * Utils: Intellij Idea
 * Description: 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandling {

    /**
     * 自定义异常
     */
    @ExceptionHandler(value = CustomException.class)
    public ResultVo processException(CustomException e) {
        log.error("位置:{} -> 错误信息:{}", e.getMethod() ,e.getLocalizedMessage());
        //bjects.requireNonNull()此方法主要用于在方法中进行参数验证。遇到要判断对象是否为空,空的时候报空指针异常的时候就可以使用这个方法。
        return ResultVoUtil.error(Objects.requireNonNull(ResultEnum.getByCode(e.getCode())));
    }

    /**
     * 拦截表单参数校验
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({BindException.class})
    public ResultVo bindException(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        return ResultVoUtil.error(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
    }

    /**
     * 拦截JSON参数校验
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultVo bindException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        return ResultVoUtil.error(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
    }

    /**
     * 参数格式错误
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResultVo methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("错误信息{}", e.getLocalizedMessage());
        return ResultVoUtil.error(ResultEnum.ARGUMENT_TYPE_MISMATCH);
    }

    /**
     * 参数格式错误
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResultVo httpMessageNotReadable(HttpMessageNotReadableException e) {
        log.error("错误信息:{}", e.getLocalizedMessage());
        return ResultVoUtil.error(ResultEnum.FORMAT_ERROR);
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultVo httpReqMethodNotSupported(HttpRequestMethodNotSupportedException e) {
        log.error("错误信息:{}", e.getLocalizedMessage());
        return ResultVoUtil.error(ResultEnum.REQ_METHOD_NOT_SUPPORT);
    }

    /**
     * 通用异常
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public ResultVo exception(Exception e) {
        e.printStackTrace();
        return ResultVoUtil.error(ResultEnum.UNKNOWN_EXCEPTION);
    }
}

①@RestControllerAdvice注解的作用:标识为一个通知方法,并且只拦截Controller层的异常.并且返回为JSON串.
②@ExceptionHandler(RuntimeException.class)注解的作用:配置异常的类型,当遇到了某种异常时在执行该方法,(RuntimeException.class)指异常类型为运行时异常
③@ResponseStatus注解待补充
到这里全局异常已经配置完成 , 如果发生异常数据我们已经通过@ExceptionHandler注解配置过, 会及时执行我们的处理方式

(4).使用自定义异常

  public void deleteUser(String id) {
        // 如果删除失败抛出异常。 -- 演示而已不推荐这样干
        if(!removeById(id)){
            throw new CustomException(ResultEnum.DELETE_ERROR, MethodUtil.getLineInfo());//调用构造方法传值
        }
    }

代码流态:
在我们业务中或者controller中使用自定义异常时候, 只需要 [throw new 自定义异常类()]就可以了, 然后我们在全局异常已经通过 @ExceptionHandler注解已经配置过[自定义CustomException异常类]也就同时触发全局异常处理方式来进一步处理解决异常

在示例代码中提到MethodUtil.getLineInfo()这个工具类方法,代码为:

/**
 * @author 王振宇
 * Date: Created in 2020-04-06
 * Utils: Intellij Idea
 * Description: 获取当前方法和行号
 */
@Slf4j
public class MethodUtil {

    /**
     * 私有化工具类 防止被实例化
     */
    private MethodUtil() {
    }

    public static String getLineInfo() {
        StackTraceElement ste = new Throwable().getStackTrace()[1];
        return ste.getFileName() + " -> " + ste.getLineNumber() + "行";
    }

}


(5). 注意

①由于项目中一般都是controller—service—mapper,所以我们只需要在controller层进行统一异常处理即可,因为service/mapper层的异常都会往上抛出至controller层
②全局异常处理机制,该功能是通过Spring利用AOP实现的.