Spring Boot 异常报告器解析-灵析社区

cathoy

创建自定义异常报告器

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.analyzersFailureAnalyzers创建的时候已经将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 定义的关闭钩子,用来优雅关机。

总结

阅读量:1410

点赞量:0

收藏量:0