本文主要内容是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重复异常、端口占用异常等等
回到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提供的一些扩展点,而在容器正常启动后,一些职能比如事件发布,都会交给容器进行