基于Spring Boot 3.1.0 系列文章
- Spring Boot 源码阅读初始化环境搭建
- Spring Boot 框架整体启动流程详解
- Spring Boot 系统初始化器详解
- Spring Boot 监听器详解
- Spring Boot banner详解
- Spring Boot 属性配置解析
- Spring Boot 属性加载原理解析
- Spring Boot 异常报告器解析
创建自定义异常报告器
FailureAnalysis 是Spring Boot 启动时将异常转化为可读消息的一种方法,系统自定义了很多异常报告器,通过接口也可以自定义异常报告器。
创建一个异常类:
public class MyException extends RuntimeException{
}
创建一个FailureAnalyzer:
public class MyFailureAnalyzer extends AbstractFailureAnalyzer<MyException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, MyException cause) {
String des = "发生自定义异常";
String action = "由于自定义了一个异常";
return new FailureAnalysis(des, action, rootFailure);
}
}
需要在Spring Boot 启动的时候抛出异常,为了测试,我们在上下文准备的时候抛出自定义异常,添加到demo中的MyApplicationRunListener中。
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("在创建和准备ApplicationContext之后,但在加载源之前调用");
throw new MyException();
}
启动后就会打印出我们的自定义异常报告器内容:
***************************
APPLICATION FAILED TO START
***************************
Description:
发生自定义异常
Action:
由于自定义了一个异常
原理分析
在之前的文章《Spring Boot 框架整体启动流程详解》,有讲到过Spring Boot 对异常的处理,如下是Spring Boot 启动时的代码:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
if (context.isRunning()) {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
}
catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
通过两个try…catch…包裹,在catch 中判断异常是否是AbandonedRunException类型,是直接抛出异常,否则的话进入handleRunFailure
中。
AbandonedRunException 异常 在 Spring Boot 处理AOT相关优化的时候会抛出
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
SpringApplicationRunListeners listeners) {
try {
try {
//处理exitCode
handleExitCode(context, exception);
if (listeners != null) {
//发送启动失败事件
listeners.failed(context, exception);
}
}
finally {
//获取报告处理器,并处理错误
reportFailure(getExceptionReporters(context), exception);
if (context != null) {
//关闭上下文
context.close();
//移除关闭钩子
shutdownHook.deregisterFailedApplicationContext(context);
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
//重新抛出异常
ReflectionUtils.rethrowRuntimeException(exception);
}
exitCode是一个整数值,默认返回0,Spring Boot会将该exitCode传递给System.exit()以作为状态码返回,如下是IDEA中停止Spring Boot 返回的退出码:
进程已结束,退出代码130
handleExitCode
进入handleExitCode
,看下是如何处理的:
private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
int exitCode = getExitCodeFromException(context, exception);
//exitCode非0
if (exitCode != 0) {
if (context != null) {
//发送ExitCodeEvent事件
context.publishEvent(new ExitCodeEvent(context, exitCode));
}
//获取当前线程的SpringBootExceptionHandler,SpringBootExceptionHandler用来处理未捕获的异常,实现了UncaughtExceptionHandler接口
handler = getSpringBootExceptionHandler();
if (handler != null) {
//添加exitCode到SpringBootExceptionHandler 中
handler.registerExitCode(exitCode);
}
}
}
private int getExitCodeFromException(ConfigurableApplicationContext context, Throwable exception) {
//从ExitCodeExceptionMapper实现中获取exitCode
int exitCode = getExitCodeFromMappedException(context, exception);
if (exitCode == 0) {
//尝试从ExitCodeGenerator实现获取exitCode
exitCode = getExitCodeFromExitCodeGeneratorException(exception);
}
return exitCode;
}
private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
//判断上下文是否是活动状态,上下文至少刷新过一次,不是就返回0
if (context == null || !context.isActive()) {
return 0;
}
//用于维护ExitCodeGenerator有序集合的组合器,ExitCodeGenerator 是一个接口,用于获取exitCode
ExitCodeGenerators generators = new ExitCodeGenerators();
//获取ExitCodeExceptionMapper类型的Bean
Collection<ExitCodeExceptionMapper> beans = context.getBeansOfType(ExitCodeExceptionMapper.class).values();
//将异常和bean包装成MappedExitCodeGenerator,排序后保存,MappedExitCodeGenerator是ExitCodeGenerator 的一个实现
generators.addAll(exception, beans);
//会循环ExitCodeGenerators 中的ExitCodeGenerator,ExitCodeGenerator会去获取ExitCodeExceptionMapper的实现,如果有一个exitCode非0则马上返回,否则返回0
return generators.getExitCode();
}
private int getExitCodeFromExitCodeGeneratorException(Throwable exception) {
//没有异常
if (exception == null) {
return 0;
}
//异常类有实现了ExitCodeGenerator 接口
if (exception instanceof ExitCodeGenerator generator) {
return generator.getExitCode();
}
//继续寻找
return getExitCodeFromExitCodeGeneratorException(exception.getCause());
}
SpringBootExceptionHandler getSpringBootExceptionHandler() {
//当前线程是主线程
if (isMainThread(Thread.currentThread())) {
//获取当前线程的SpringBootExceptionHandler
return SpringBootExceptionHandler.forCurrentThread();
}
return null;
}
listeners.failed
在处理完exitCode后,继续执行listeners.failed(context, exception)
,这里就跟以前一样,循环SpringApplicationRunListener实现
reportFailure
Spring Boot 首先从spring.factories
获取所有的SpringBootExceptionReporter
实现,FailureAnalyzers
是其唯一实现,其用于加载和执行FailureAnalyzer
reportFailure 循环执行获取的SpringBootExceptionReporter,如果发送异常成功,则会向之前的SpringBootExceptionHandler
中记录,表示该异常已经捕获处理
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
try {
for (SpringBootExceptionReporter reporter : exceptionReporters) {
//如果异常发送成功
if (reporter.reportException(failure)) {
//记录异常
registerLoggedException(failure);
return;
}
}
}
catch (Throwable ex) {
// 如果上述操作发生异常,还是会继续执行
}
//记录error级别日志
if (logger.isErrorEnabled()) {
logger.error("Application run failed", failure);
registerLoggedException(failure);
}
}
reporter.reportException
在reportFailure中,通过reporter.reportException(failure)判断异常是否发送成功,进入代码,由于该Demo 只有一个FailureAnalyzers实现,所以进入到FailureAnalyzers的reportException中:
public boolean reportException(Throwable failure) {
//循环调用加载的FailureAnalyzer实现的analyze方法
FailureAnalysis analysis = analyze(failure, this.analyzers);
//加载FailureAnalysisReporter实现,组装具体错误信息,并打印日志
return report(analysis);
}
this.analyzers
在FailureAnalyzers
创建的时候已经将FailureAnalyzer
实现从spring.factories
中加载
下面的代码将循环调用加载的FailureAnalyzer实现的analyze方法,返回一个包装了异常描述、发生异常的动作、原始异常
信息的对象
private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {
for (FailureAnalyzer analyzer : analyzers) {
try {
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
catch (Throwable ex) {
logger.trace(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);
}
}
return null;
}
此处Spring Boot 建议自定义的FailureAnalyzer 通过继承AbstractFailureAnalyzer
来实现,Spring Boot 自带的FailureAnalyzer确实也是这样的,但是你也可以直接实现FailureAnalyzer 接口。AbstractFailureAnalyzer中会筛选出需要关注的异常,而直接实现FailureAnalyzer 接口,需要自行在方法中处理。
随后将返回的FailureAnalysis实现通过FailureAnalysisReporter组装打印到客户端
private boolean report(FailureAnalysis analysis) {
//FailureAnalysisReporter也是从spring.factories中加载,可见也可以自定义
List<FailureAnalysisReporter> reporters = this.springFactoriesLoader.load(FailureAnalysisReporter.class);
if (analysis == null || reporters.isEmpty()) {
return false;
}
for (FailureAnalysisReporter reporter : reporters) {
reporter.report(analysis);
}
return true;
}
在该Demo中,只有一个FailureAnalysisReporter实例LoggingFailureAnalysisReporter
public void report(FailureAnalysis failureAnalysis) {
//如果是debug级别,则会打印堆栈信息
if (logger.isDebugEnabled()) {
logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());
}
//如果是error级别,还会打印组装好的错误信息
if (logger.isErrorEnabled()) {
logger.error(buildMessage(failureAnalysis));
}
}
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();
}
关闭上下文、移除钩子
context.close() 如果上下文不为空,则关闭上下文,并且移除关闭钩子。
shutdownHook.deregisterFailedApplicationContext(context) 用来将之前在SpringApplicationShutdownHook 钩子中注册的上下文移除。
SpringApplicationShutdownHook 是Spring Boot 定义的关闭钩子,用来优雅关机。