1. 概述

本文深入探讨 Java 中的全局异常处理机制。我们会先回顾异常的基本概念和处理方式,然后重点分析如何通过 UncaughtExceptionHandler 实现全局异常捕获。

对于异常处理的更多基础知识,可参考 Java 异常处理详解

如果你在项目中经常遇到线上报一堆堆栈却不知道从哪下手,或者想统一收集未捕获异常做监控报警——那这篇文章就是为你准备的。✅


2. 什么是异常?

异常是在程序运行或编译期间发生的非正常状态,通常是由于代码违反了 Java 语言的语义规则所导致。

Java 中的异常主要分为两类:

  • 检查异常(Checked Exceptions):继承自 Exception 类但非 RuntimeException 子类。编译器强制要求必须处理,否则无法通过编译。
  • 非检查异常(Unchecked Exceptions):即运行时异常,继承自 RuntimeException。编译器不强制捕获或声明,容易被忽略,也是我们踩坑的高发区 ❗

⚠️ 虽然运行时异常不需要显式处理,但这不代表它们不重要。相反,很多生产问题都源于未妥善处理的 NullPointerExceptionArithmeticException 等。


3. 异常处理机制

Java 的健壮性很大程度上得益于其完善的异常处理框架。它允许程序在出错时优雅降级,而不是直接崩溃退出。

当异常发生时,JVM 或当前执行的方法会创建一个 Exception 对象,其中包含了异常类型、堆栈信息等关键数据。我们的任务就是“接住”这个对象并决定如何应对。

3.1 try-catch 块

最基础的异常捕获方式是使用 try-catch

String string = "01, , 2010";
DateFormat format = new SimpleDateFormat("MM, dd, yyyy");
Date date;
try {
    date = format.parse(string);
} catch (ParseException e) {
    System.out.println("ParseException caught!");
}

上面这段代码尝试解析一个格式错误的日期字符串,parse() 方法会抛出 ParseException,我们用 catch 捕获并打印提示。

这是局部处理的经典模式,适用于你能预知异常场景的情况。

3.2 throw 与 throws 关键字

有时候你不打算自己处理异常,而是选择向上抛出,交由调用方处理:

public class ExceptionHandler {

    public static void main(String[] args) {

        String strDate = "01, , 2010";
        String dateFormat = "MM, dd, yyyy";
        
        try {
            Date date = new DateParser().getParsedDate(strDate, dateFormat);
        } catch (ParseException e) {
            System.out.println("The calling method caught ParseException!");
        }
    }
}

class DateParser {
    
    public Date getParsedDate(String strDate, String dateFormat) throws ParseException {
        DateFormat format = new SimpleDateFormat(dateFormat);
        
        try {
            return format.parse(strDate);
        } catch (ParseException parseException) {
            throw parseException;
        }        
    }
    
}

这里 getParsedDate() 方法声明了 throws ParseException,表示它可能抛出该异常。main() 方法作为调用者负责捕获处理。

这种方式适合分层架构中将异常传递到更高层统一处理的场景。


4. 全局异常处理器

尽管我们可以用 try-catch 处理已知异常,但总有漏网之鱼——尤其是那些未被捕获的运行时异常。这时候如果程序直接崩掉,用户体验极差,也难以定位问题。

为此,Java 提供了 Thread.UncaughtExceptionHandler 接口,用于捕获线程中未处理的异常。

核心机制

  • Thread.UncaughtExceptionHandler 是一个函数式接口,定义了一个方法:

    void uncaughtException(Thread t, Throwable e)
    
  • ✅ Java 5 起,Thread 类提供了静态方法设置全局处理器:

    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
    
  • ✅ 当某个线程因未捕获异常而终止时,JVM 会自动调用该处理器的 uncaughtException() 方法

实战示例

下面是一个典型的全局异常处理器实现:

public class GlobalExceptionHandler {

    public static void main(String[] args) {

        Handler globalExceptionHandler = new Handler();
        Thread.setDefaultUncaughtExceptionHandler(globalExceptionHandler);
        new GlobalExceptionHandler().performArithmeticOperation(10, 0);
    }

    public int performArithmeticOperation(int num1, int num2) {
        return num1 / num2; // 会抛出 ArithmeticException
    }
}

class Handler implements Thread.UncaughtExceptionHandler {

    private static Logger LOGGER = LoggerFactory.getLogger(Handler.class);

    public void uncaughtException(Thread t, Throwable e) {
        LOGGER.info("Unhandled exception caught in thread: " + t.getName(), e);
        // 可以在这里做日志记录、上报监控系统、发送告警邮件等
    }
}

输出结果类似:

INFO  Handler - Unhandled exception caught in thread: main
java.lang.ArithmeticException: / by zero
    at com.example.GlobalExceptionHandler.performArithmeticOperation(...)
    at com.example.GlobalExceptionHandler.main(...)

也能处理检查异常?

你可能会问:全局处理器不是只处理运行时异常吗?其实不然。

只要异常最终没有被 catch,即使它是检查异常(如 IOException),也会被 UncaughtExceptionHandler 捕获:

public static void main(String[] args) throws Exception {
    Handler globalExceptionHandler = new Handler();
    Thread.setDefaultUncaughtExceptionHandler(globalExceptionHandler);
    Path file = Paths.get("/nonexistent/file.txt");
    Files.delete(file); // 抛出 IOException
}

虽然 main 方法声明了 throws Exception,但由于没有调用者处理,该异常最终仍被视为“未捕获”,从而触发全局处理器。

注意事项 ⚠️

  • 全局异常处理器虽然强大,但它打破了“就近处理”的设计原则。
  • 它更像是最后一道防线,而不是替代 try-catch 的方案。
  • 不建议在处理器中做复杂逻辑(如网络请求),因为此时线程已处于异常状态,资源可能不可用。

5. 总结

本文系统介绍了 Java 中的异常分类、基本处理方式以及全局异常处理器的使用。

关键点回顾:

  • ✅ 异常分 checked 和 unchecked 两类,后者更易被忽视但危害更大
  • try-catchthrows 是常规处理手段
  • UncaughtExceptionHandler 是处理未捕获异常的终极武器
  • ✅ 通过 Thread.setDefaultUncaughtExceptionHandler() 可设置全局处理器
  • ✅ 即使是检查异常,若最终未被捕获,也能被全局处理器捕获

💡 实际项目中建议结合使用:业务层做好异常处理,同时配置全局处理器用于兜底日志记录和告警。

本文示例代码可在 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-exceptions-2


原始标题:Java Global Exception Handler