Spring Boot 异常处理

2019-04-03 来源: binarylei 发布在  https://www.cnblogs.com/binarylei/p/10646567.html

Spring Boot 异常处理

本节介绍一下 Spring Boot 启动时是如何处理异常的?核心类是 SpringBootExceptionReporter 和 SpringBootExceptionHandler。

一、Spring Boot 异常处理流程

public ConfigurableApplicationContext run(String... args) {
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    try {
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    } catch (Throwable ex) {
        // 处理异常
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

run 方法中的异常处理都交给 handleRunFailure 完成。

private void handleRunFailure(ConfigurableApplicationContext context,
        Throwable exception,
        Collection<SpringBootExceptionReporter> exceptionReporters,
        SpringApplicationRunListeners listeners) {
    try {
        try {
            // 1. ExitCodeGenerators 根据异常获取是正常不是异常退出
            handleExitCode(context, exception);
            if (listeners != null) {
                listeners.failed(context, exception);
            }
        } finally {
            // 2. SpringBootExceptionReporter 处理异常报告
            reportFailure(exceptionReporters, exception);
            if (context != null) {
                context.close();
            }
        }
    } catch (Exception ex) {
        logger.warn("Unable to close ApplicationContext", ex);
    }
    // 3. 重新报出异常,由 SpringBootExceptionHandler 处理
    ReflectionUtils.rethrowRuntimeException(exception);
}

handleRunFailure 中主要依赖了三个组件完成异常的处理:

  • SpringBootExceptionReporter 生成错误报告并处理,主要是用于输出日志。
  • SpringBootExceptionHandler 实现了 Thread#UncaughtExceptionHandler 接口,可以在线程异常关闭的时候进行回调。主要用于退出程序 System.exit(xxx)
  • SpringApplicationRunListeners Spring Boot 事件机制

1.1 handleExitCode

handleExitCode 根据异常的类型决定如何退出程序,并将 exitCode(0 或 1) 退出码注册到 SpringBootExceptionHandler 上。

private void handleExitCode(ConfigurableApplicationContext context,
        Throwable exception) {
    // 根据异常判断是正常退出还是异常退出
    int exitCode = getExitCodeFromException(context, exception);
    if (exitCode != 0) {
        if (context != null) {
            context.publishEvent(new ExitCodeEvent(context, exitCode));
        }
        SpringBootExceptionHandler handler = getSpringBootExceptionHandler();
        if (handler != null) {
            // 正常退出或异常退出,System.exit(exitCode) 用
            handler.registerExitCode(exitCode);
        }
    }
}

getExitCodeFromException 根据异常判断是正常退出还是异常退出,委托给了 ExitCodeGenerators,最后将退出码注册到 SpringBootExceptionHandler 上。

1.2 reportFailure

private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters,
        Throwable failure) {
    try {
        for (SpringBootExceptionReporter reporter : exceptionReporters) {
            if (reporter.reportException(failure)) {
                registerLoggedException(failure);
                return;
            }
        }
    } catch (Throwable ex) {
    }
    if (logger.isErrorEnabled()) {
        logger.error("Application run failed", failure);
        registerLoggedException(failure);
    }
}

reportFailure 委托 SpringBootExceptionReporter 处理异常,并将异常注册到 SpringBootExceptionHandler 上。

二、ExitCodeGenerators

private int getExitCodeFromException(ConfigurableApplicationContext context,
        Throwable exception) {
    // ExitCodeGenerators 处理异常
    int exitCode = getExitCodeFromMappedException(context, exception);
    // 如果没有分析出来,则判断这个异常本身是实现了 ExitCodeGenerator 接口,继续分析
    if (exitCode == 0) {
        exitCode = getExitCodeFromExitCodeGeneratorException(exception);
    }
    return exitCode;
}

// 从 context 中获取所有的 ExitCodeExceptionMapper 来分析异常
private int getExitCodeFromMappedException(ConfigurableApplicationContext context,
        Throwable exception) {
    if (context == null || !context.isActive()) {
        return 0;
    }
    ExitCodeGenerators generators = new ExitCodeGenerators();
    Collection<ExitCodeExceptionMapper> beans = context
            .getBeansOfType(ExitCodeExceptionMapper.class).values();
    // 将 exception 和 ExitCodeExceptionMapper 封装成 ExitCodeGenerator 注册到 generators 中
    generators.addAll(exception, beans);
    return generators.getExitCode();
}

// 异常本身实现了 ExitCodeGenerator 接口
private int getExitCodeFromExitCodeGeneratorException(Throwable exception) {
    if (exception == null) {
        return 0;
    }
    if (exception instanceof ExitCodeGenerator) {
        return ((ExitCodeGenerator) exception).getExitCode();
    }
    return getExitCodeFromExitCodeGeneratorException(exception.getCause());
}

ExitCodeGenerator 和 ExitCodeExceptionMapper 接口如下,ExitCodeGenerators 管理多个 ExitCodeGenerator。Spring 将 exception 和 ExitCodeExceptionMapper 封装成 ExitCodeGenerator 注册到 ExitCodeGenerators 中便于统一处理。

@FunctionalInterface
public interface ExitCodeGenerator {
    int getExitCode();
}

@FunctionalInterface
public interface ExitCodeExceptionMapper {
    int getExitCode(Throwable exception);
}

三、SpringBootExceptionReporter

SpringBootExceptionReporter 也是在 spring.factories 中配置的,默认实现为 FailureAnalyzers。FailureAnalyzers 持有多个 FailureAnalyzer 来分析异常生成 FailureAnalysis 报告,由 FailureAnalysisReporter 处理。这些类都位于 org.springframework.boot.diagnostics 包下。具体的配置如下:

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

FailureAnalyzers 处理流程也非常简单。

四、SpringBootExceptionHandler

Thread#UncaughtExceptionHandler 处理线程异常关闭时未处理的异常:https://www.cnblogs.com/jadic/p/3532580.html

SpringBootExceptionHandler 实现了 Thread#UncaughtExceptionHandler 接口,在线程关闭时退出程序。

@Override
public void uncaughtException(Thread thread, Throwable ex) {
    try {
        if (isPassedToParent(ex) && this.parent != null) {
            this.parent.uncaughtException(thread, ex);
        }
    } finally {
        this.loggedExceptions.clear();
        if (this.exitCode != 0) {
            System.exit(this.exitCode);
        }
    }
}

那 SpringBootExceptionHandler 是怎么注册到线程上的呢?实际上在初始化类的时候就注册到线程上了。

// 初始化类的时候就实例了 SpringBootExceptionHandler
private static LoggedExceptionHandlerThreadLocal handler = new LoggedExceptionHandlerThreadLocal();

private static class LoggedExceptionHandlerThreadLocal
        extends ThreadLocal<SpringBootExceptionHandler> {
    @Override
    protected SpringBootExceptionHandler initialValue() {
        SpringBootExceptionHandler handler = new SpringBootExceptionHandler(
                Thread.currentThread().getUncaughtExceptionHandler());
        // 将 SpringBootExceptionHandler 注册到当前线程上
        Thread.currentThread().setUncaughtExceptionHandler(handler);
        return handler;
    }
}

获取 SpringBootExceptionHandler 实例:

static SpringBootExceptionHandler forCurrentThread() {
    return handler.get();
}

每天用心记录一点点。内容也许不重要,但习惯很重要!

相关文章