• JAVA中该如何正确处理异常?
  • 发布于 1个月前
  • 76 热度
    0 评论
异常处理是我们每天都需要面对的问题,但很多代码中往往会出现:
反例 1
重复且繁琐的的异常处理逻辑
@Slf4j
public class DuplicatedExceptionHandlerExample {
    // 堆代码 duidaima.com
    private UserService userService;
    public User query(String id) {
        try {
            return userService.query(id);
        } catch (Exception e) {
            log.error("query error, userId: {}", id, e);
            return null;
        }
    }

    public User create(String id) {
        try {
            return userService.create(id);
        } catch (Exception e) {
            log.error("query error, userId: {}", id, e);
            return null;
        }
    }
}
反例 2:
异常被吞掉或者丢失部分信息
@Slf4j
public class ExceptionShouldLogOrThrowExample {
    private UserService userService;
    public User query(String id) {
        try {
            return userService.query(id);
        } catch (Exception e) {
            // 异常被吞并, 问题被隐藏
            return null;
        }
    }

    public User create(String id) {
        try {
            return userService.create(id);
        } catch (Exception e) {
            // 堆代码 duidaima.com
            // 堆栈丢失, 后续难以定位问题
            log.error("query error, userId: {}, error: {}", id,e.getMessage() );
            return null;
        }
    }
}
反例 3:
对外抛出未知异常, 导致调用方序列化失败
public class OpenAPIService {

    public void handle(){
        // HSF 服务对外抛出 client 中未定义的异常, 调用方反序列化失败
        throw new InternalSystemException("");
    }
}
正确方式1.通过 AOP 统一异常处理
1.避免未知异常抛给调用方, 将未知异常转为 Result 或者通用异常类型
2.统一异常日志的打印和监控

正确方式2.处理 Checked Exception
Checked Exception 是在编译期要求必须处理的异常,也就是非 RuntimeException 类型的异常,但 Java Checked 的异常给接口的调用者造成了一定的负担,导致异常声明层层传递,如果顶层能够处理该异常,我们可以通过 lombok 的 @SneakyThrows 注解规避 Checked exception

正确方式3.Try catch 线程逻辑
反例:
@RequiredArgsConstructor
public class ThreadNotTryCatch {
    private final ExecutorService executorService;
    public void handle() {
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                // 未捕获异常, 线程直接退出, 异常信息丢失
                remoteInvoke();
            }
        });
    }
}
正例:
@RequiredArgsConstructor
@Slf4j
public class ThreadNotTryCatch {
    private final ExecutorService executorService;

    public void handle() {
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    remoteInvoke();
                } catch (Exception e) {
                    log.error("handle failed", e);
                }
            }
        });
    }
}
特殊异常的处理
InterruptedException 一般是上层调度者主动发起的中断信号,例如某个任务执行超时,那么调度者通过将线程置为 interuppted 来中断任务,对于这类异常我们不应该在 catch 之后忽略,应该向上抛出或者将当前线程置为 interuppted。

反例:
public class InterruptedExceptionExample {
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    public void handleWithTimeout() throws InterruptedException {
        Future<?> future = executorService.submit(() -> {
            try {
                // sleep 模拟处理逻辑
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("interrupted");
            }
            System.out.println("continue task");
            // 异常被忽略, 继续处理
        });
        // 等待任务结果, 如果超过 500ms 则中断
        Thread.sleep(500);
        if (!future.isDone()) {
            System.out.println("cancel");
            future.cancel(true);
        }
    }
}
避免 catch Error
不要吞并 Error,Error 设计本身就是区别于异常,一般不应该被 catch,更不能被吞掉。举个例子,OOM 有可能发生在任意代码位置,如果吞并 Error,让程序继续运行,那么以下代码的 start 和 end 就无法保证一致性。
public class ErrorExample {
    private Date start;
    private Date end;

    public synchronized void update(long start, long end) {
        if (start > end) {
            throw new IllegalArgumentException("start after end");
        }
        this.start = new Date(start);
        // 如果 new Date(end) 发生 OOM, start 有可能大于 end
        this.end = new Date(end);
    }
}


用户评论