异常体系介绍及SpringBoot全局异常处理解决方案

0、前面文章说过最近公司搭建新系统,这次我们聊聊关于全局异常处理。

1、异常体系和分类

1.1、先说说异常体系

所有的异常都是继承Throwable而来,其中Throwable分为Error和Exception

Error是错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时, 如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

Exception,是另外一个非常重要的异常子类。它规定的异常是程序本身可以处理的异常。Exception类分解成两个分支,一支派生于RuntimeException,另一支派生于其他异常。异常和错误的区别是,异常是可以被处理的,而错误是没法处理的。

1.2、异常分类

异常分类有可检查异常、不可检查异常 ;

可检查异常就是编译器要求你必须处置的异常。不知道你编程的时候有没有遇到过,你写的某段代码,编译器要求你必须要对这段代码try…catch,或者throws exception, 如果你遇见过,没错,这就是检查异常,也就是说,你代码还没运行呢,编译器就会检查你的代码,会不会出现异常,要求你对可能出现的异常必须做出相应的处理。

对检查异常(checked exception)的几种处理方式:

  • 1、继续抛出,消极的方法,一直可以抛到java虚拟机来处理,就是通过throws exception抛出。注意,对于检查的异常必须处理,或者必须捕获或者必须抛出

如何区分检查异常:除了RuntimeException与其子类,以及错误(Error),其他的都是检查异常(绝对的大家族)。

不可检查异常就是编译器不要求强制处置的异常,虽然你有可能出现错误,但是我不会在编译的时候检查,没必要,也不可能。为什么呢?你想想非检查异常都有哪些?NullPointerException,IndexOutOfBoundsException,VirtualMachineError等,这些异常你编译的时候检查吗?那我还要不要运行了,等死人啊。再说了,明明可以运行时检查,都在编译的时候检查,你写的代码还能看吗?而且有些异常只能在运行时才能检查出来,比如空指针,堆溢出等。

对未检查的异常(unchecked exception )的几种处理方式:

  • 1、继续抛出;
  • 2、不处理。 一般我们是不处理的,因为你很难判断会出什么问题,而且有些异常你也无法运行时处理,
    比如空指针,需要人手动的去查找。而且,捕捉异常并处理的代价远远大于直接抛出。

2、常见的异常

  • 输入输出异常:IOException
  • 算术异常类:ArithmeticExecption
  • 空指针异常类:NullPointerException
  • 类型强制转换异常:ClassCastException
  • 操作数据库异常:SQLException
  • 文件未找到异常:FileNotFoundException
  • 数组负下标异常:NegativeArrayException
  • 数组下标越界异常:ArrayIndexOutOfBoundsException
  • 违背安全原则异常:SecturityException
  • 文件已结束异常:EOFException
  • 字符串转换为数字异常:NumberFormatException
  • 方法未找到异常:NoSuchMethodException

3、现在说说统一处理异常的必要性:

  1. 避免try…catch过多,使得代码可维护性、可读性降低
  2. 统一出现异常后返回给前端的数据格式
  3. Validator参数校验器的时候,参数校验不通过会抛出异常,无法用try…catch捕获,只能使用全局异常处理器。

4、统一处理异常解决方案:

  • 统一异常拦截(GlobalExceptionHandler)、
  • 统一定义异常code枚举类(ExceptionCodeEnum)、
  • 统一定义业务异常通用类(BusinessException)。
@Slf4j
@Component
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {@Override
   protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
   log.error("REST API exception handler got a message not readable exception, message:{},[request:{}]}", ExceptionUtils.getStackTrace(ex), request);
   log.info("参数不能读异常:{}\nat:{}", ExceptionUtils.getStackTrace(ex));
   return new ResponseEntity<>(Result.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), ex.getMessage(), null, null), OK);
    }
    
    @Override
    protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    log.error("REST API exception handler got a message not writable exception:\n" + ExceptionUtils.getStackTrace(ex));
    return new ResponseEntity<>(Result.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), ex.getMessage(), null, null), OK);
    }
    
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
    MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    String message = ex.getFieldError().getDefaultMessage();
    String method = ex.getParameter().getMethod().getName();
    String field = ex.getFieldError().getField();
    log.error("except-error-monitor, @Valid方法参数校验失败,method: {}, field: {}, detail: {}", method, field, ex.getMessage());
    log.info("参数异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n   "));
    return new ResponseEntity<>(Result.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), "参数校验失败:" + message, null, null), OK);
    }
    
    @ExceptionHandler({IllegalArgumentException.class})
    protected ResponseEntity<Object> handlerArgumentException(Exception ex, WebRequest request) {
    // 异常的简略信息输出至告警群
    log.error("except-error-monitor, 参数异常:{}", ex.getMessage());
    log.info("参数异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n   "));
    return new ResponseEntity<>(Result.of(PARAM_ERROR.getCode(), "参数校验失败:" + ex.getMessage(), null, null), OK);
    }
    
    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<Object> handleBusinessException(BusinessException ex, WebRequest request) {
    // 异常的简略信息输出至告警群
    if (!ex.getErrCode().equals(BIZ_ERROR_NOT_ALERT.getCode())) {
    log.error("except-error-monitor, 业务异常:{}", ex.getMessage());
    }
    log.info("业务异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n   "));
    return new ResponseEntity<>(Result.of(Optional.ofNullable(ex.getErrCode()).orElse(BIZ_ERROR.getCode()), ex.getMessage(), null, null), OK);
    }/*
    
    */
    @ExceptionHandler(ClientHandlerException.class)
    protected ResponseEntity<Object> handleClientHandlerException(BusinessException ex, WebRequest request) {
    log.info("网络异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n   "));
    return new ResponseEntity<>(Result.of(NETWORK_ERROR.getCode(), NETWORK_ERROR.getDesc(), null, null), OK);
    }@ExceptionHandler(RedissonLockException.class)
    protected ResponseEntity<Object> handleRedissonLockException(RedissonLockException ex, WebRequest request) {
        log.error("except-error-monitor, 分布式锁异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n   "));
        return new ResponseEntity<>(Result.of(REPEAT_EXECUTE), OK);
    }@ExceptionHandler(Throwable.class)
    protected ResponseEntity<Object> handleGeneralException(Throwable ex, WebRequest request) {
        log.error("未知异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n   "));
        return new ResponseEntity<>(Result.of(SERVICE_ERROR), OK);
    }/**
     * 授权异常统一处理
     * @Author YangYaNan
     * @Date Created in 18:30 2022/6/17
     * @Description
     * @param ex, request
     * @return org.springframework.http.ResponseEntity<java.lang.Object>
     **/
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<Object> accessDeniedException(AccessDeniedException ex, WebRequest request){
        log.error("except-error-monitor, 用户授权异常:{}, [request:{}]", ExceptionUtils.getStackTrace(ex), request);
        return new ResponseEntity<>(Result.of(FORBIDDEN_OPERATION), FORBIDDEN);
    }/**
     * 认证异常统一处理
     * @Author YangYaNan
     * @Date Created in 18:31 2022/6/17
     * @Description
     * @param ex, request
     * @return org.springframework.http.ResponseEntity<java.lang.Object>
     **/
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<Object> authenticationException(AuthenticationException ex, WebRequest request){
        log.error("except-error-monitor, 用户认证异常:{}, [request:{}]", ExceptionUtils.getStackTrace(ex), request);
        return new ResponseEntity<>(LoginFailureHandler.onAuthenticationFailure(ex), OK);
    }
}
​
​
​
public enum ExceptionCodeEnum {SUCCESS(200, "请求成功"),
    ACCESS_ERROR(503, "当前访问流量过大,请稍后重试"),// 2xxx 业务异常
    BIZ_ERROR(2000, "业务操作失败"),
    ILLEGAL_OPERATION_ERROR(2001, "非正常操作提示"),
    // 不告警
    BIZ_ERROR_NOT_ALERT(2999, "业务操作失败"),
    FILE_FORMAT_ERROR(2008, "文件格式错误"),
    FURTHER_EXECUTE_SERVICE(2009, "需要补充资料"),// 4xxx 客户端异常
    BAD_REQUEST(4001, "非法请求"),
    FORBIDDEN_OPERATION(4003, "无权限操作"),
    TIMEOUT(4004, "请求超时"),
    UNAUTHORIZED_ACCESS(401, "未找到当前登录用户信息"),
    ERROR_TOKEN(4006,"无效的TOKEN"),
    NETWORK_ERROR(4007,"网络异常"),// 5xxx 第三方服务异常
    RPC_ERROR(5000, "调用远程接口失败"),// 6xxx 基础中间件异常
    DB_ERROR(6000, "数据库操作失败"),// 7xxx 数据问题
    PARAM_ERROR(7000, "参数错误"),
    EXIST(7004, "请求数据已存在");
​
​
    /**
     * 获取code
     *
     * @return int code
     */
    public int getCode() {
        return code;
    }/**
     * 获取desc
     *
     * @return string desc
     */
    public String getDesc() {
        return desc;
    }/**
     * @param code 代码
     * @param desc 详情
     */
    ExceptionCodeEnum(final int code, final String desc) {
        this.code = code;
        this.desc = desc;
    }private int code;
    private String desc;}
​
​
​
​
public class BusinessException extends RuntimeException {
    private Integer errCode;
    private String errMsg;
    private Integer httpStatusCode;public BusinessException(String errMsg) {
        super(errMsg);
        this.errMsg = errMsg;
    }public BusinessException(String errMsg, Throwable cause) {
        super(errMsg, cause);
        this.errMsg = errMsg;
    }public BusinessException(Integer errCode, String errMsg) {
        this(errMsg);
        this.errCode = errCode;
    }public BusinessException(ExceptionCodeEnum code) {
        this(code.getCode(), code.getDesc());
    }public BusinessException(ExceptionCodeEnum code, String errMsg) {
        this(code.getCode(), errMsg);
    }public BusinessException(Integer errCode, String errMsg, Throwable cause) {
        this(errMsg, cause);
        this.errCode = errCode;
    }public BusinessException(Integer errCode, String errMsg, Integer httpStatusCode) {
        this(errCode, errMsg);
        this.httpStatusCode = httpStatusCode;
    }public BusinessException(Integer errCode, String errMsg, Integer httpStatusCode, Throwable cause) {
        this(errCode, errMsg, cause);
        this.httpStatusCode = httpStatusCode;
    }public Integer getErrCode() {
        return this.errCode;
    }public String getErrMsg() {
        return this.errMsg;
    }public Integer getHttpStatusCode() {
        return this.httpStatusCode;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白de成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值