写点什么

SpringBoot 之自定义启动异常堆栈信息打印

用户头像
false℃
关注
发布于: 2021 年 02 月 20 日

在 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 的管理。

发布于: 2021 年 02 月 20 日阅读数: 28
用户头像

false℃

关注

还未添加个人签名 2018.11.13 加入

还未添加个人简介

评论

发布
暂无评论
SpringBoot之自定义启动异常堆栈信息打印