自定义异常

简单记录异常及自定义异常

Posted by yyconstantine on September 17, 2019

自定义异常


异常处理机制的作用

先啰嗦几句基础的信息,也方便自己回顾。

首先,异常是如何处理的?

  • 抛出异常:创建异常对象,交由系统处理
  • 捕获异常:寻找合适的异常处理器处理异常,否则终止程序执行

那,异常处理有什么用呢?

  • 说明抛出什么异常 –> 异常类型
  • 说明哪里异常 –> 异常堆栈
  • 说明为什么异常 –> 异常信息

既然异常这么方便,那有没有什么可以遵循的规范呢?

1.具体明确:抛出的异常应能通过异常类名和信息准确说明异常的类型和产生异常的原因

2.提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题

3.延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常


Exception和Error

从定义上区分:

  • Error:程序无法处理的系统错误,编译器不做检查 –> 无法捕获或处理
  • Exception:程序可以处理的异常,部分在编译器被检查 –> 可以捕获或处理
    • RuntimeException:不可预知的,程序应当自行避免
    • 非RuntimeException:可预知的,编译器在编译器进行检查

常见的Error及Exception有:

  • RuntimeException:
    • NullPointerException –> 空指针引用异常
    • ClassCastException –> 类型强制转换异常
    • IllegalArgumentException –> 传递非法参数异常
    • IndexOutOfBoundsException –> 下标越界异常
    • NumberFormatException –> 数字格式异常
  • 非RuntimeException:
    • ClassNotFoundException –> 找不到指定class异常
    • IOException –> IO操作异常
  • Error:
    • NoClassDefFoundError –> 找不到class定义的异常
      • 类依赖的class或jar不存在
      • 类文件存在,但存在在不同的域中
      • 大小写问题,javac编译的时候是无视大小写的,很有可能编译出来的class文件与想要的不一样
    • StackOverflowError –> 一般是由递归过深导致栈资源耗尽抛出的异常
    • OutOfMemoryError –> 内存溢出异常

自定义异常

一个业务需求整理如下:

  • 方法内存在嵌套的事务
  • sql执行正常,但执行结果为空(effected rows为0)时,需要回滚所有事务

根据需求描述,我们可明确👇:

  • 在执行结果为空时抛出一个自定义异常,确保方法内事务的一致性
  • 抛出的异常不应该打印错误堆栈息(错误堆栈信息不提供任何有用结果)

则我们的代码👇:

  • 编写一个自定义异常类,由于我们不希望其打印错误堆栈信息,则我们可以重写其fillInStackTrace()方法和toString()方法;其中fillInStackTrace()方法定义堆栈打印信息,默认为打印全部调用链,toString()方法定义异常输出格式;这里我们简单的输出枚举定义的信息:

    public class SelfTransactionException extends RuntimeException {
      
        private int code;
      
        private String msg;
      
        private final ErrorEnum errorEnum;
      
        public SelfTransactionException(ErrorEnum errorEnum) {
            super(errorEnum.toString());
            this.errorEnum = errorEnum;
            this.msg = errorEnum.getMsg();
            this.code = errorEnum.getCode();
        }
      
        @Override
        public synchronized Throwable fillInStackTrace() {
            if (this.code == 505) {
                return this;
            }
            return super.fillInStackTrace();
        }
      
        @Override
        public String toString() {
            return "错误信息为:{" +
                    "code=" + this.code +
                    ", msg='" + this.msg + '\'' +
                    '}';
        }
    }
    
  • 错误枚举,其中定义了一个事务回滚的异常

    public enum ErrorEnum {
          
        TRANSACTION_ERROR(505, "事务回滚");
      
        private int code;
        private String msg;
      
        ErrorEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
      
        public int getCode() {
            return code;
        }
      
        public String getMsg() {
            return msg;
        }
      
    }
    
  • 在事务注解声明所要抛出的异常:

    @Transactional(rollbackFor = SelfTransactionException.class)
    public boolean isDone(String params) {
        if(updateMethod() && insertMethod()) {
            return true;
        }
        // 执行结果返回异常则抛出错误,事务回滚
        throw new SelfTransactionException(ErrorEnum.TRANSACTION_ERROR);
    }
    
  • 简单说下自定义异常的继承:

    • Exception:知道业务上存在问题,(几乎是一定)会出现异常,要求开发人员明确该异常及进行处理,则继承Exception,显示地声明该异常需要处理
      • 问题:耦合度较高,多层调用时如果进行修改影响调用链
    • RuntimeException:业务比较稳定,正常情况下不会出现问题,则可不显示地声明该异常,只做自己的try-catch处理

补充

对于自定义异常的处理:

  • 在最初使用自定义异常处理时,由于只对自定义的异常进行了捕获及处理,所以通过postman测试接口的时候,只看到返回了500错误,但本地/测试环境均没有报错,摸索发现,由于定义了全局的异常处理(即添加@RestControllerAdvice),但只处理了自定义的异常,所以Exception经过了全局的异常处理器,但没有被处理,所以在本地没有打印错误堆栈信息

    @Slf4j
    @RestControllerAdvice
    public class SettleAccountExceptionHandler {
      
        /**
         * 自定义异常
         */
        @ExceptionHandler(MyException.class)
        public Result handleMyException(SettleAccountException exception) {
            log.error("请求错误,错误码为{},错误信息为{}", exception.getCode(), exception.getMsg());
            return Result.error(exception.getCode(), exception.getMsg());
        }
      
        @ExceptionHandler(NoHandlerFoundException.class)
        public Result handleNoHandlerFoundException(NoHandlerFoundException exception) {
            log.error(exception.getMessage(), exception);
            return Result.error(ErrorEnum.PATH_NOT_FOUND);
        }
      
        @ExceptionHandler(Exception.class)
        public Result handleAllException(Exception exception) {
            log.error(exception.getMessage(), exception);
            return Result.error(ErrorEnum.UNKNOWN);
        }
      
    }
    

其他

在使用try-catch块代替if-else时,可能会对性能产生影响。那大概是怎样的影响呢?

  • if-else性能消耗较小
  • try-catch消耗性能的主要因素为:
    • try-catch块影响JVM优化
    • 异常对象实例需要保存栈快照等信息