SpringBoot源码解析(十三)SpringBootExceptionReporter

本文主要内容是SpringBoot项目启动过程的异常分析器,也就是SpringBootExceptionReporter,回想我们启动项目的时候,是否会因为缺少数据库配置、端口占用、bean命名重复等各种原因,导致项目启动失败呢,比如端口占用的情况下启动项目,控制台会打印如下日志

***************************
APPLICATION FAILED TO START
***************************

Description:

The Tomcat connector configured to listen on port 9099 failed to start. The port may already be in use or the connector may be misconfigured.

Action:

Verify the connector's configuration, identify and stop any process that's listening on port 9099, or configure this application to listen on another port.

这段日志会指出启动失败的原因,以及建议的解决方案,比如添加某些配置,或者将某配置设置为true等等
SpringBootExceptionReporter的作用就是对启动过程的异常进行分析、报告,涉及到的代码在SpringApplication类的run方法中:

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

getSpringFactoriesInstances到classpath下的META-INF/spring.factories文件中找SpringBootExceptionReporter的实现类,然后将新建的容器作为参数调用其构造方法

最终只找到一个FailureAnalyzers,在spring-boot包下

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

查看FailureAnalyzers类的构造方法

final class FailureAnalyzers implements SpringBootExceptionReporter {
    private static final Log logger = LogFactory.getLog(FailureAnalyzers.class);
    private final ClassLoader classLoader;
    private final List<FailureAnalyzer> analyzers;

    FailureAnalyzers(ConfigurableApplicationContext context) {
        this(context, (ClassLoader)null);
    }

    FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
        Assert.notNull(context, "Context must not be null");
        this.classLoader = classLoader != null ? classLoader : context.getClassLoader();
        this.analyzers = this.loadFailureAnalyzers(this.classLoader);
        this.prepareFailureAnalyzers(this.analyzers, context);
    }

使用容器的类加载器,去加载具体的异常分析器,进入loadFailureAnalyzers方法

    private List<FailureAnalyzer> loadFailureAnalyzers(ClassLoader classLoader) {
        List<String> analyzerNames = SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, classLoader);
        List<FailureAnalyzer> analyzers = new ArrayList();
        Iterator var4 = analyzerNames.iterator();

        while(var4.hasNext()) {
            String analyzerName = (String)var4.next();

            try {
                Constructor<?> constructor = ClassUtils.forName(analyzerName, classLoader).getDeclaredConstructor();
                ReflectionUtils.makeAccessible(constructor);
                analyzers.add((FailureAnalyzer)constructor.newInstance());
            } catch (Throwable var7) {
                logger.trace("Failed to load " + analyzerName, var7);
            }
        }

        AnnotationAwareOrderComparator.sort(analyzers);
        return analyzers;
    }

同样是到spring.factories中加载FailureAnalyzer类型的实现类,并实例化
这次共找到了17个实现类,其中13个位于spring-boot包下,4个位于spring-boot-autoconfigure包下,看名字大多数还是比较熟悉的,比如循环依赖异常、beanDefinition重复异常、端口占用异常等等
SpringBoot源码解析(十三)SpringBootExceptionReporter_第1张图片
回到FailureAnalyzers构造方法,加载到FailureAnalyzer列表后,调用prepareFailureAnalyzers方法

    private void prepareFailureAnalyzers(List<FailureAnalyzer> analyzers, ConfigurableApplicationContext context) {
        Iterator var3 = analyzers.iterator();

        while(var3.hasNext()) {
            FailureAnalyzer analyzer = (FailureAnalyzer)var3.next();
            this.prepareAnalyzer(context, analyzer);
        }

    }

循环FailureAnalyzer列表,调用prepareAnalyzer方法

    private void prepareAnalyzer(ConfigurableApplicationContext context, FailureAnalyzer analyzer) {
        if (analyzer instanceof BeanFactoryAware) {
            ((BeanFactoryAware)analyzer).setBeanFactory(context.getBeanFactory());
        }

        if (analyzer instanceof EnvironmentAware) {
            ((EnvironmentAware)analyzer).setEnvironment(context.getEnvironment());
        }
    }

这个方法查看如果FailureAnalyzer实现了BeanFactoryAware接口和EnvironmentAware接口,将对应的BeanFactory和Environment赋给它
之所以有这个步骤,是因为有些异常分析器处理异常信息的过程中,有可能要依赖容器或者项目的环境,而Aware接口正常的执行时机是在容器刷新的时候,如果在Aware的过程中,或者在这之前就发生了异常,这一部分FailureAnalyzer就没办法正常工作了,所以需要提前将依赖设置进来

需要注意的是,这里设置进来的environment,是直接从容器中取的,它是在容器的构造函数中新建的,并不是我们之前经过了一系列加工的environment,虽然后面会用我们的environment将容器中的替换掉,但是这些FailureAnalyzer持有的environment并没有一起更新,所以这个步骤我个人认为是有点问题的

prepareAnalyzer完成后,加载SpringBootExceptionReporter的流程就结束了,接下来看catch中是怎么使用这么分析器的,进入handleRunFailure方法

    private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
        try {
            try {
                this.handleExitCode(context, exception);
                if (listeners != null) {
                    listeners.failed(context, exception);
                }
            } finally {
                this.reportFailure(exceptionReporters, exception);
                if (context != null) {
                    context.close();
                }

            }
        } catch (Exception var9) {
            logger.warn("Unable to close ApplicationContext", var9);
        }

        ReflectionUtils.rethrowRuntimeException(exception);
    }

先看第一行handleExitCode,它根据exitCode来决定是否发送退出事件,同时也提供了一些接口让我们可以自定义exitCode

    private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
        int exitCode = this.getExitCodeFromException(context, exception);
        if (exitCode != 0) {
            if (context != null) {
                context.publishEvent(new ExitCodeEvent(context, exitCode));
            }

            SpringBootExceptionHandler handler = this.getSpringBootExceptionHandler();
            if (handler != null) {
                handler.registerExitCode(exitCode);
            }
        }
    }

getExitCodeFromException方法根据容器的状态以及异常类型来获取exitCode

    private int getExitCodeFromException(ConfigurableApplicationContext context, Throwable exception) {
        int exitCode = this.getExitCodeFromMappedException(context, exception);
        if (exitCode == 0) {
            exitCode = this.getExitCodeFromExitCodeGeneratorException(exception);
        }

        return exitCode;
    }

先看getExitCodeFromMappedException方法,如果容器还没有启动,直接返回0,否则从容器中获取ExitCodeExceptionMapper类型的Bean,赋给ExitCodeGenerators,并调用其getExitCode方法获取退出码

    private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
        if (context != null && context.isActive()) {
            ExitCodeGenerators generators = new ExitCodeGenerators();
            Collection<ExitCodeExceptionMapper> beans = context.getBeansOfType(ExitCodeExceptionMapper.class).values();
            generators.addAll(exception, beans);
            return generators.getExitCode();
        } else {
            return 0;
        }
    }

ExitCodeExceptionMapper是一个函数式接口,提供了从异常中获取退出码的方法,我们可以通过实现这个接口,来自定义退出码

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

ExitCodeGenerators将传进来的ExitCodeExceptionMapper封装成内部类MappedExitCodeGenerator,它实现了ExitCodeGenerator接口,这个接口也是函数式接口,返回一个退出码,那么这个封装,肯定是内部调用了ExitCodeExceptionMapper的方法了

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

通过getExitCode方法遍历这个列表,根据if里面的条件,其实不确定最终的响应码到底是正数还是负数,正负码之间并没有相对的优先级,而程序最终关心的是退出码是否为0

    public int getExitCode() {
        int exitCode = 0;
        Iterator var2 = this.generators.iterator();

        while(var2.hasNext()) {
            ExitCodeGenerator generator = (ExitCodeGenerator)var2.next();

            try {
                int value = generator.getExitCode();
                if (value > 0 && value > exitCode || value < 0 && value < exitCode) {
                    exitCode = value;
                }
            } catch (Exception var5) {
                exitCode = exitCode != 0 ? exitCode : 1;
                var5.printStackTrace();
            }
        }

        return exitCode;
    }

回到getExitCodeFromException方法,通过上面这一步拿到的退出码如果是0,会再根据异常做一次判断,因为有可能第一步容器并没有激活,直接就返回0了,调用getExitCodeFromExitCodeGeneratorException方法,如果异常类实现了ExitCodeGenerator 接口,就调用其getExitCode方法获取退出码

    private int getExitCodeFromExitCodeGeneratorException(Throwable exception) {
        if (exception == null) {
            return 0;
        } else {
            return exception instanceof ExitCodeGenerator ? ((ExitCodeGenerator)exception).getExitCode() : this.getExitCodeFromExitCodeGeneratorException(exception.getCause());
        }
    }

最终返回的退出码如果不是0,就通过容器发布一个ExitCodeEvent事件,并将退出码注册到SpringBootExceptionHandler上,用于后续的日志记录

退出码处理完毕,回到handleRunFailure方法,接下来看listeners 如果不为空,就通过它来发布启动失败事件

    private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
        try {
            try {
                this.handleExitCode(context, exception);
                if (listeners != null) {
                    listeners.failed(context, exception);
                }
            } finally {
                this.reportFailure(exceptionReporters, exception);
                if (context != null) {
                    context.close();
                }

            }
        } catch (Exception var9) {
            logger.warn("Unable to close ApplicationContext", var9);
        }

        ReflectionUtils.rethrowRuntimeException(exception);
    }

此时listeners 肯定不为空的,在前面的文章,我们已经通过它发布了应用启动事件ApplicationStartingEvent,以及环境准备就绪事件ApplicationEnvironmentPreparedEvent,而这里就是要发布应用启动失败相关的事件,进入failed方法

    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        Iterator var3 = this.listeners.iterator();

        while(var3.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var3.next();
            this.callFailedListener(listener, context, exception);
        }
    }

跟之前一样,这个listeners列表只有一个元素EventPublishingRunListener,将它传给callFailedListener方法

    private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context, Throwable exception) {
        try {
            listener.failed(context, exception);
        } catch (Throwable var6) {
            if (exception == null) {
                ReflectionUtils.rethrowRuntimeException(var6);
            }

            if (this.log.isDebugEnabled()) {
                this.log.error("Error handling failed", var6);
            } else {
                String message = var6.getMessage();
                message = message != null ? message : "no error message";
                this.log.warn("Error handling failed (" + message + ")");
            }
        }
    }

最后相当于是调用了EventPublishingRunListener的fail方法

    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
        if (context != null && context.isActive()) {
            context.publishEvent(event);
        } else {
            if (context instanceof AbstractApplicationContext) {
                Iterator var4 = ((AbstractApplicationContext)context).getApplicationListeners().iterator();

                while(var4.hasNext()) {
                    ApplicationListener<?> listener = (ApplicationListener)var4.next();
                    this.initialMulticaster.addApplicationListener(listener);
                }
            }

            this.initialMulticaster.setErrorHandler(new EventPublishingRunListener.LoggingErrorHandler());
            this.initialMulticaster.multicastEvent(event);
        }

    }

这里先初始化了一个事件ApplicationFailedEvent ,然后判断容器是否已经启动了,如果是,就由容器来负责事件的发布,否则将容器中已经存在的事件监听器注册到当前的事件多播器中,跟之前几个事件的发布流程一样,继续由它来发布事件

处理退出码、发布启动失败事件等流程结束后,分析异常原因,并关闭容器

	......
   finally {
        this.reportFailure(exceptionReporters, exception);
        if (context != null) {
            context.close();
        }
    }
    ......

看下reportFailure的实现,入参就是开始时找到的SpringBootExceptionReporter,只有一个实现FailureAnalyzers

    private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
        try {
            Iterator var3 = exceptionReporters.iterator();

            while(var3.hasNext()) {
                SpringBootExceptionReporter reporter = (SpringBootExceptionReporter)var3.next();
                if (reporter.reportException(failure)) {
                    this.registerLoggedException(failure);
                    return;
                }
            }
        } catch (Throwable var5) {
            ;
        }

        if (logger.isErrorEnabled()) {
            logger.error("Application run failed", failure);
            this.registerLoggedException(failure);
        }

    }

进入FailureAnalyzers类的reportException方法

    public boolean reportException(Throwable failure) {
        FailureAnalysis analysis = this.analyze(failure, this.analyzers);
        return this.report(analysis, this.classLoader);
    }

先调用analyze,用之前找到的17个异常解析器去分析异常原因,直到某个解析器返回的解析结果不为空,就结束遍历

    private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {
        Iterator var3 = analyzers.iterator();

        while(var3.hasNext()) {
            FailureAnalyzer analyzer = (FailureAnalyzer)var3.next();

            try {
                FailureAnalysis analysis = analyzer.analyze(failure);
                if (analysis != null) {
                    return analysis;
                }
            } catch (Throwable var6) {
                logger.debug("FailureAnalyzer " + analyzer + " failed", var6);
            }
        }

        return null;
    }

文章开头的异常日志中,启动失败的原因以及建议的解决方案,就是封装在这个解析结果中

public class FailureAnalysis {
    private final String description;
    private final String action;
    private final Throwable cause;

解析的过程由每个解析器去实现,根据异常的类型来决定是否返回结果,然后将解析结果传给FailureAnalyzers类的report方法

    private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {
        List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class, classLoader);
        if (analysis != null && !reporters.isEmpty()) {
            Iterator var4 = reporters.iterator();

            while(var4.hasNext()) {
                FailureAnalysisReporter reporter = (FailureAnalysisReporter)var4.next();
                reporter.report(analysis);
            }
            return true;
        } else {
            return false;
        }
    }

该方法先到spring.factories中找FailureAnalysisReporter的实现类,它决定了异常分析结果的报告形式,默认只找到一个LoggingFailureAnalysisReporter,定义在spring-boot包下

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

也就是最终调用了LoggingFailureAnalysisReporter的report方法

    public void report(FailureAnalysis failureAnalysis) {
        if (logger.isDebugEnabled()) {
            logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());
        }

        if (logger.isErrorEnabled()) {
            logger.error(this.buildMessage(failureAnalysis));
        }

    }

根据传进来的结果,调用buildMessage构建输出信息,这个内容就很熟悉了,就是前面日志里展示的异常报告格式

    private String buildMessage(FailureAnalysis failureAnalysis) {
        StringBuilder builder = new StringBuilder();
        builder.append(String.format("%n%n"));
        builder.append(String.format("***************************%n"));
        builder.append(String.format("APPLICATION FAILED TO START%n"));
        builder.append(String.format("***************************%n%n"));
        builder.append(String.format("Description:%n%n"));
        builder.append(String.format("%s%n", failureAnalysis.getDescription()));
        if (StringUtils.hasText(failureAnalysis.getAction())) {
            builder.append(String.format("%nAction:%n%n"));
            builder.append(String.format("%s%n", failureAnalysis.getAction()));
        }

        return builder.toString();
    }

总结来说,SpringBoot启动过程的异常分析,其实分了几个阶段,依赖于容器的启动状态,在容器还没初始化的时候发生异常,依然走之前的逻辑,也没办法应用到Spring提供的一些扩展点,而在容器正常启动后,一些职能比如事件发布,都会交给容器进行

你可能感兴趣的:(java,springboot源码,spring,boot,java,spring)