SpringBoot 之自定义启动异常堆栈信息打印
在 SpringBoot 项目启动过程中,当一些配置或者其他错误信息会有一些的规范的提示信息
```
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
```
在 SpringBoot 中其实现原理是什么,我们该如何自定义异常信息呢
# 1、SpringBoot 异常处理的源码分析
在 springboot 启动的核心方法 run 中会加载所有的 SpringBootExceptionReporter
```
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
```
调用了 getSpringFactoriesInstances 方法
```
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
```
其主要通过 Spring 的 Factories 机制来加载
```
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
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;
}
```
在 try catch 中,catch 会打印异常信息
```
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
try {
try {
handleExitCode(context, exception);
if (listeners != null) {
listeners.failed(context, exception);
}
}
finally {
reportFailure(exceptionReporters, exception);
if (context != null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
```
```
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
try {
for (SpringBootExceptionReporter reporter : exceptionReporters) {
if (reporter.reportException(failure)) {
registerLoggedException(failure);
return;
}
}
}
catch (Throwable ex) {
// Continue with normal handling of the original failure
}
if (logger.isErrorEnabled()) {
logger.error("Application run failed", failure);
registerLoggedException(failure);
}
}
```
遍历 exceptionReporters,打印日常信息
![SpringBootExceptionReporter-](https://img-blog.csdnimg.cn/20201116122134620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNjI3NjE5Mw==,size_16,color_FFFFFF,t_70#pic_center)
- SpringBootExceptionReporter 是一个回调接口,用于支持对 SpringApplication 启动错误的自定义报告。里面就一个报告启动失败的方法。
- 其实现类:org.springframework.boot.diagnostics.FailureAnalyzers
用于触发从 spring.factories 加载的 FailureAnalyzer 和 FailureAnalysisReporter 实例。
# 2、如何自定义异常信息
-
```
/**
* <p>
*
* <p>
*
* @author: xuwd
* @time: 2020/11/16 10:52
*/
public class WannaStopException extends RuntimeException {
}
```
自定义异常信息打印
```
public class StopFailureAnalyzer
extends AbstractFailureAnalyzer<WannaStopException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, WannaStopException cause) {
for (StackTraceElement stackTraceElement : cause.getStackTrace()) {
if (stackTraceElement.getClassName().equals("com.pigx.demo.Config21")) {
return new FailureAnalysis("A 想停止", "别要 A 了", cause);
}
}
return null;
}
}
```
接下来令他生效,通过上面分析可以可看出需要通过 AutoConfigurationImportSelector,类似于自定义 SpringBoot Starter AutoConfiguration 的形式,我们需要在 META-INF/spring.factories 文件内进行定义,如下所示:
接着在合适的地方抛出 WannaStopException 异常
# 总结
在 springboot 启动过程中会先对异常信息进行补捕获,对进行日志格式处理的日志进行处理;其核心是通过 SpringBootExceptionReporter 回调及 sping-spi bean 的管理。
版权声明: 本文为 InfoQ 作者【false℃】的原创文章。
原文链接:【http://xie.infoq.cn/article/82c705917a6949ca1010b62d7】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论