异常处理容易犯的错

236 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

捕获和处理异常容易犯的错

  1. 不在业务代码层面考虑异常处理,仅在框架层面粗犷捕获和处理异常。

在实际项目中,异常分为业务异常和系统异常。如果统一处理异常,可能会产生异常不到的问题,比如有些业务需要根据异常做分支业务流程处理;如异常捕获了,事务无法自动回滚。

Spring框架中,可以在Controller层做统一的异常处理,如使用@RestControllerAdvice + @ExceptionHandler,处理过程中可以分为自定义异常(输出Warn级别日志)和非自定义异常(输出Error级别定义)。

  1. 捕获了异常后直接生吞,从而导致发生了异常而无感知
  2. 丢弃异常的原始信息,不方便排查原因
  3. 抛出异常时不指定任何消息

catch异常的最佳实践

  1. 通过日志正确记录异常原始信息
  2. 转换,即转换新的异常抛出
  3. 重试,即重试之前的操作。比如远程调用服务端过载超时的情况,盲目重试会让问题更严重,需要考虑当前情况是否适合重试。
  4. 恢复,即尝试进行降级处理,或使用默认值来替代原始数据。

小心finally中的异常

不管是否遇到异常,逻辑完成后都要释放资源,这时可以使用 finally代码块而跳过使用 catch 代码块。

如果在finally处理过程中,又发生了异常,则会覆盖原来的异常。因为方法只能抛出一个异常。正确做法有:

  1. finally代码块自己负责异常捕获和处理。
  2. 将finally异常通过addSuppressed 方法添加到主异常中。这也是 try-with-resources 语句的做法,对于实现了 AutoCloseable 接口的资源,建议使用 try-with-resources 来释放资源,否则也可能会产生刚才提到的,释放资源时出现 的异常覆盖主异常的问题。

try-with-resources 语句:

try (TestResource testResource = new TestResource()){ 
}

千万别把异常定义为静态变量

提交线程池的任务出了异常会怎么样

线程池里的线程若抛出异常未catch处理的话,当前线程会退出,并创建新的线程。这样,线程池的线程就达不到复用的作用

演示:

    public static void main(String[] args){
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        IntStream.range(0, 10).forEach(i -> executorService.execute(() -> {
            if (i == 5) {
                throw new RuntimeException("error");
            }
            System.out.println(Thread.currentThread().getName() + "done" + i);
        }));
    }
// 输出结果
pool-1-thread-1done0
pool-1-thread-1done1
pool-1-thread-1done2
pool-1-thread-1done3
pool-1-thread-1done4
pool-1-thread-2done6
pool-1-thread-2done7
pool-1-thread-2done8
pool-1-thread-2done9
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: error
	at com.carrywei.bread.concurrent.ThreadExceptionDemo.lambda$null$0(ThreadExceptionDemo.java:30)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

从输出结果看出,线程池内部在发生异常之后,又重新新建了一个线程。

解决方法:

  1. 在方法内部处理异常

  2. 设置UncaughtExceptionHandler

    a. 调用线程的setUncaughtExceptionHandler 方法,设置单个异常处理方法。 b. 全局设置:Thread.setDefaultUncaughtExceptionHandler

参考:《Java 业务开发常见错误 100 例》的【12丨异常处理:别让自己在出问题的时候变为瞎子】]