文章目录
前言
本节就来简单看看SpringBoot的FailureAnalyzer调用流程,学习下SpringBoot处理异常的手段之一。(本节以启动异常为例)
FailureAnalyzers介绍
FailureAnalyzers属性
在讲FailureAnalyzer
之前,必须先讲一讲FailureAnalyzers
,因为FailureAnalyzer
就是在FailureAnalyzers
中构造的,下面先来看看FailureAnalyzers
的属性:
- 毫无疑问,classLoader就是用来加载List
的,即便不往下看源码,你也一定能猜到一半逻辑了。 - 另外,稍微注意下FailureAnalyzers是实现SpringBootExceptionReporter接口的。
FailureAnalyzers构造
直接来到SpringApplication#run
方法,找到如下两段代码:
org.springframework.boot.SpringApplication#run(java.lang.String...)
...(略)
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
...(略)
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
...(略)
跟进getSpringFactoriesInstances方法:
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>, java.lang.Class<?>[], java.lang.Object...)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 从spring.factories加载
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 根据@Order排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
其中SpringFactoriesLoader.loadFactoryNames
就是加载spring.factories
的配置:
- 这一点在初始化器那里也提到过,这里就不再赘述了。
- 实际上SpringBootExceptionReporter的实现类就只有FailureAnalyzers一个,其它SpringBoot自带的spring.factories文件都没有再指定其它实现了。
继续跟进createSpringFactoriesInstances方法:
org.springframework.boot.SpringApplication#createSpringFactoriesInstances
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
// 使用类加载器构造对象
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
// 将构造后的对象存储到集合中
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
根据构建Constructor传进的参数,我们可以肯定最终反射到如下FailureAnalyzers构造方法:
org.springframework.boot.diagnostics.FailureAnalyzers#FailureAnalyzers(org.springframework.context.ConfigurableApplicationContext, java.lang.ClassLoader)
FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
Assert.notNull(context, "Context must not be null");
this.classLoader = (classLoader != null) ? classLoader : context.getClassLoader();
// 构造FailureAnalyzer集合
this.analyzers = loadFailureAnalyzers(this.classLoader);
// 对特定FailureAnalyzer初始化
prepareFailureAnalyzers(this.analyzers, context);
}
loadFailureAnalyzers
等一下再看。
跟进prepareFailureAnalyzers看看:
org.springframework.boot.diagnostics.FailureAnalyzers#prepareFailureAnalyzers
private void prepareFailureAnalyzers(List<FailureAnalyzer> analyzers, ConfigurableApplicationContext context) {
for (FailureAnalyzer analyzer : analyzers) {
prepareAnalyzer(context, analyzer);
}
}
继续跟进prepareAnalyzer方法:
org.springframework.boot.diagnostics.FailureAnalyzers#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());
}
}
- 会对特定实现的报告器注入一些Spring容器的属性。
- 关于Aware,在前一篇博客"感应器"-Aware中有简单讲到过,有兴趣的可以去看一下。
FailureAnalyzer简介
FailureAnalyzer接口
FailureAnalyzer就只有一个接口方法:
@FunctionalInterface
public interface FailureAnalyzer {
package org.springframework.boot.diagnostics;
/**
* Returns an analysis of the given {@code failure}, or {@code null} if no analysis
* was possible.
* @param failure the failure
* @return the analysis or {@code null}
*/
FailureAnalysis analyze(Throwable failure);
}
analyze
就是分析异常的方法,分析之后返回一个FailureAnalysis
对象。
FailureAnalyzer子类
在FailureAnalyzer子类中,我们能看到几个比较眼熟的东西:
- 我们平时端口重复、找不到bean等错误,就是由上面的Analyzer分析的。
另外,在上面的图中,我们看到AbstractFailureAnalyzer
算是一个顶层骨架实现,我们来一次过看看它的所有代码:
public abstract class AbstractFailureAnalyzer<T extends Throwable> implements FailureAnalyzer {
@Override
public FailureAnalysis analyze(Throwable failure) {
// 从异常中获取当前FailureAnalyzer感兴趣的异常
T cause = findCause(failure, getCauseType());
// 返回null则表示不对该异常链感兴趣
if (cause != null) {
// 调用子类实现分析
return analyze(failure, cause);
}
return null;
}
/**
* Returns an analysis of the given {@code rootFailure}, or {@code null} if no
* analysis was possible.
* 调用子类实现分析
* @param rootFailure the root failure passed to the analyzer
* @param cause the actual found cause
* @return the analysis or {@code null}
*/
protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause);
/**
* Return the cause type being handled by the analyzer. By default the class generic
* is used.
* 获取泛型中指定类型的字节码对象
* @return the cause type
*/
@SuppressWarnings("unchecked")
protected Class<? extends T> getCauseType() {
return (Class<? extends T>) ResolvableType.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric();
}
/**
* 判断此异常是否是当前FailureAnalyzer实现感兴趣的异常
* @param failure 异常
* @param type 感兴趣的异常字节码对象
* @param <E> 泛型,由子类指定
* @return 如果不为null则表示对此异常感兴趣
*/
@SuppressWarnings("unchecked")
protected final <E extends Throwable> E findCause(Throwable failure, Class<E> type) {
while (failure != null) {
// 判断是否对此异常感兴趣
if (type.isInstance(failure)) {
return (E) failure;
}
// "刨开"异常链
failure = failure.getCause();
}
return null;
}
}
FailureAnalyzer的泛型就是用来指定此分析器感兴趣的异常,这一点和ApplicationListener的泛型效果一致。
FailureAnalyzer构造
视角回到FailureAnalyzers的构造方法:
org.springframework.boot.diagnostics.FailureAnalyzers#FailureAnalyzers(org.springframework.context.ConfigurableApplicationContext, java.lang.ClassLoader)
FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
Assert.notNull(context, "Context must not be null");
this.classLoader = (classLoader != null) ? classLoader : context.getClassLoader();
this.analyzers = loadFailureAnalyzers(this.classLoader);
prepareFailureAnalyzers(this.analyzers, context);
}
跟进loadFailureAnalyzers方法:
org.springframework.boot.diagnostics.FailureAnalyzers#loadFailureAnalyzers
private List<FailureAnalyzer> loadFailureAnalyzers(ClassLoader classLoader) {
// 读取spring.factories配置
List<String> analyzerNames = SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, classLoader);
List<FailureAnalyzer> analyzers = new ArrayList<>();
// 使用classLoader构造实例
for (String analyzerName : analyzerNames) {
try {
Constructor<?> constructor = ClassUtils.forName(analyzerName, classLoader).getDeclaredConstructor();
ReflectionUtils.makeAccessible(constructor);
analyzers.add((FailureAnalyzer) constructor.newInstance());
}
catch (Throwable ex) {
logger.trace("Failed to load " + analyzerName, ex);
}
}
// 根据@Order排序
AnnotationAwareOrderComparator.sort(analyzers);
return analyzers;
}
这次是从两个spring.factories文件中读取配置的:
总计17个:
FailureAnalysis简介
看类图一目了然:
- 作用就是保存FailureAnalyzer分析完后的异常描述、动作、对象。
以上都只是简单介绍,下面人为制造一些错误,来看看整个流程是什么样的。
跟进源码
我这里就拿端口重复的做例子,拿什么占用端口这随便你发挥,你把端口封掉也8是问题~
下面,我就以应用端口已经被占用为前提(请确保不会产生其它错误),跟进源码。
在文末还会补充一张源码追踪的流程图。
启动应用后,会来到SpringApplication#run
的如下位置,我们就从这里开始跟进:
注意,在进入这个断点之前,控制台就已经输出了
ERROR
日志,注意这个日志并不是由异常报告器生成的。
跟进handleRunFailure方法,我们就能看到五大步骤:
org.springframework.boot.SpringApplication#handleRunFailure
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
try {
try {
// 获取并处理ExitCode
handleExitCode(context, exception);
if (listeners != null) {
// 其实也是广播事件
listeners.failed(context, exception);
}
}
finally {
// 会调用之前讲过的reportException方法。
reportFailure(exceptionReporters, exception);
if (context != null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
// 如果属于特定类型异常,则抛出
ReflectionUtils.rethrowRuntimeException(exception);
}
下面就对这五大步骤进行分析。
handleExitCode--获取并处理ExitCode
org.springframework.boot.SpringApplication#handleExitCode
private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
// 获取exitCode,一般情况下,正常退出都是返回0
int exitCode = getExitCodeFromException(context, exception);
if (exitCode != 0) {
if (context != null) {
// 广播ExitCodeEvent
context.publishEvent(new ExitCodeEvent(context, exitCode));
}
// 构造异常处理器
SpringBootExceptionHandler handler = getSpringBootExceptionHandler();
if (handler != null) {
// handler注册exitCode
handler.registerExitCode(exitCode);
}
}
}
- 关于事件的广播与监听,不懂的可以参考【监听器分析】这一篇。
跟进getExitCodeFromException方法:
org.springframework.boot.SpringApplication#getExitCodeFromException
private int getExitCodeFromException(ConfigurableApplicationContext context, Throwable exception) {
// 从ExitCodeExceptionMapper实现上获取exitCode
int exitCode = getExitCodeFromMappedException(context, exception);
if (exitCode == 0) {
// 从ExitCodeGenerator实现上获取exitCode
exitCode = getExitCodeFromExitCodeGeneratorException(exception);
}
return exitCode;
}
- 如果你不是很懂上面的注释,先不用急,文末会扩展一个自定义exitCode的例子。
跟进getExitCodeFromMappedException方法看看:
org.springframework.boot.SpringApplication#getExitCodeFromMappedException
private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
if (context == null || !context.isActive()) {
return 0;
}
ExitCodeGenerators generators = new ExitCodeGenerators();
// 获取ExitCodeExceptionMapper的实现集合(用户也可以自定义ExitCodeExceptionMapper实现)
// 在默认环境中(或当前调试环境中),ExitCodeExceptionMapper实现的数量为0,即没有。
Collection<ExitCodeExceptionMapper> beans = context.getBeansOfType(ExitCodeExceptionMapper.class).values();
generators.addAll(exception, beans);
// 尝试回调集合中所有ExitCodeExceptionMapper实现的getExitCode
return generators.getExitCode();
}
跟进getExitCode方法:
org.springframework.boot.ExitCodeGenerators#getExitCode
public int getExitCode() {
// 默认为0,表示正常退出
int exitCode = 0;
for (ExitCodeGenerator generator : this.generators) {
try {
// 调用子类实现的getExitCode方法
int value = generator.getExitCode();
// 注意这里的一个细节,获取的value必须 > 0才算有效
if (value > 0 && value > exitCode || value < 0 && value < exitCode) {
exitCode = value;
}
}
catch (Exception ex) {
exitCode = (exitCode != 0) ? exitCode : 1;
ex.printStackTrace();
}
}
// 当前实验环境下最终返回0,因为没有任何子类实现。
return exitCode;
}
之后会返回到getExitCodeFromException方法中,剩下的getExitCodeFromExitCodeGeneratorException
方法原理跟getExitCodeFromException
差不多,由于我们没有ExitCodeGenerator
相关实现,会继续返回0。
拿到exitCode后,返回到org.springframework.boot.SpringApplication#handleExitCode
方法,在广播ExitCodeEvent事件、注册exitCode之后,handleExitCode的工作就完成了。
listeners.failed--广播ApplicationFailedEvent事件
视角重新回到handleRunFailure方法,我们接着看listeners.failed(context, exception);
做了什么:
org.springframework.boot.SpringApplicationRunListeners#failed
public void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
- 忘记说了,SpringApplicationRunListener实现只有一个,就是它本身。
跟进callFailedListener方法:
org.springframework.boot.SpringApplicationRunListeners#callFailedListener
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
try {
// 跟进这里
listener.failed(context, exception);
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message != null) ? message : "no error message";
this.log.warn("Error handling failed (" + message + ")");
}
}
}
跟进failed方法,发现原来是广播ApplicationFailedEvent事件:
org.springframework.boot.context.event.EventPublishingRunListener#failed
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
- 关于事件的广播与监听,不懂的可以参考【监听器分析】这一篇,这里不再赘述。
reportFailure--分析、报告异常
视角重新回到handleRunFailure方法,找到reportFailure方法,这个方法可以说是本章核心了,跟进它:
org.springframework.boot.SpringApplication#reportFailure
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
try {
// 上面已经说过,SpringBootExceptionReporter 实现类只有一个FailureAnalyzers
for (SpringBootExceptionReporter reporter : exceptionReporters) {
// 尝试报告这个异常,成功则返回true
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);
}
}
跟进reportException方法:
org.springframework.boot.diagnostics.FailureAnalyzers#reportException
@Override
public boolean reportException(Throwable failure) {
// 分析异常
FailureAnalysis analysis = analyze(failure, this.analyzers);
// 报告器报告异常
return report(analysis, this.classLoader);
}
analyze
先跟进analyze方法:
org.springframework.boot.diagnostics.FailureAnalyzers#analyze
private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {
// 逐一判断哪个FailureAnalyzer对此异常感兴趣
for (FailureAnalyzer analyzer : analyzers) {
try {
// 调用子类实现
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
catch (Throwable ex) {
logger.debug("FailureAnalyzer " + analyzer + " failed", ex);
}
}
return null;
}
- 该方法返回null则表示没有任何分析器对此异常感兴趣。
我们再来看看都有哪些FailureAnalyzer实现:
- 其中ConnectorStartFailureAnalyzer就对实验的异常感兴趣。
跟进analyze方法,首先来到AbstractFailureAnalyzer的实现:
org.springframework.boot.diagnostics.AbstractFailureAnalyzer#analyze(java.lang.Throwable)
@Override
public FailureAnalysis analyze(Throwable failure) {
// 从异常中获取当前FailureAnalyzer感兴趣的异常
T cause = findCause(failure, getCauseType());
// 返回null则表示不对该异常链感兴趣
if (cause != null) {
// 调用子类实现分析
return analyze(failure, cause);
}
return null;
}
- findCause方法在上文介绍FailureAnalyzer时已经详细说过了,这里不再赘述。
继续跟进analyze方法:
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer#analyze
@Override
protected FailureAnalysis analyze(Throwable rootFailure, ConnectorStartFailedException cause) {
return new FailureAnalysis("The Tomcat connector configured to listen on port " + cause.getPort()
+ " failed to start. The port may already be in use or the" + " connector may be misconfigured.",
"Verify the connector's configuration, identify and stop any process " + "that's listening on port "
+ cause.getPort() + ", or configure this application to listen on another port.",
cause);
}
最终构造出FailureAnalysis对象如下,相信应该有不少人对其中的文字都有点眼熟:
report
拿到分析完后的FailureAnalysis方法,我们返回到reportException方法,跟进report方法:
org.springframework.boot.diagnostics.FailureAnalyzers#report
private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {
// 加载FailureAnalysisReporter实现
List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class,
classLoader);
if (analysis == null || reporters.isEmpty()) {
return false;
}
for (FailureAnalysisReporter reporter : reporters) {
reporter.report(analysis);
}
return true;
}
FailureAnalysisReporter的实现默认同样只有一个:
跟进report方法,直接看关联的所有核心方法:
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter#report
@Override
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(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();
}
- 就是构造字符串然后打印,相信上面的文字各位肯定都非常眼熟了~
上面代码执行完后,控制台输出如下:
[main] ERROR o.s.b.diagnostics.LoggingFailureAnalysisReporter -
***************************
APPLICATION FAILED TO START
***************************
Description:
The Tomcat connector configured to listen on port 8080 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 8080, or configure this application to listen on another port.
在这之后发生的事,前面代码注释里我也写了,不再赘述。
context.close()
视角重新返回handleRunFailure方法,我们跟进context.close()
方法:
org.springframework.context.support.AbstractApplicationContext#close
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
doClose();
// If we registered a JVM shutdown hook, we don't need it anymore now:
// We've already explicitly closed the context.
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
}
- 如果你不知道ShutdownHook是个啥,建议补充下java基础,这是一个在jvm关闭前会回调的玩意。
跟进doClose方法:
org.springframework.context.support.AbstractApplicationContext#doClose
protected void doClose() {
// Check whether an actual close attempt is necessary...
// cas操作更新上下文closed状态
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
// JMX相关处理
LiveBeansView.unregisterApplicationContext(this);
try {
// 广播ContextClosedEvent
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 关停所有生命周期处理器
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// 销毁所有单例bean
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// 关停web容器
onClose();
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// 更改上下文active状态
this.active.set(false);
}
}
至此,整个SpringBoot应用已经可以看做"死透了"的状态了。
rethrowRuntimeException
视角重新返回到handleRunFailure方法,跟进ReflectionUtils.rethrowRuntimeException(exception);
看看:
org.springframework.util.ReflectionUtils#rethrowRuntimeException
public static void rethrowRuntimeException(Throwable ex) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
throw new UndeclaredThrowableException(ex);
}
没什么好说的,上面的分析器、报告器把异常轮了一遍之后,后续说不定还有东西要处理这个异常,所以继续把异常抛出。
由于我们的异常被继续抛出了,所以最终exitCode会变成1,控制台后续输出如下:
Process finished with exit code 1
至此,端口重复占用的异常就处理完毕了。
流程图
回顾下整个流程,花了张流程图如下(只显示几个核心步骤):
扩展
自定义exitCode
从上面的getExitCodeFromMappedException方法不难看出,其实我们可以自定义exitCode,只要添加如下代码即可:
import org.springframework.boot.ExitCodeExceptionMapper;
import org.springframework.boot.web.embedded.tomcat.ConnectorStartFailedException;
import org.springframework.stereotype.Component;
@Component
public class MyExitCodeExceptionMapper implements ExitCodeExceptionMapper {
@Override
public int getExitCode(Throwable exception) {
if (exception instanceof ConnectorStartFailedException) {
// 注意要 > 0,上文已经解释过为什么
return 6;
}
return 0;
}
}
最终控制台输入如下:
Process finished with exit code 10
自定义异常报告器
新建自定义异常报告器:
MyExceptionReporter.java
import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.boot.web.embedded.tomcat.ConnectorStartFailedException;
import org.springframework.context.ConfigurableApplicationContext;
public class MyExceptionReporter implements SpringBootExceptionReporter {
private ConfigurableApplicationContext context;
MyExceptionReporter(ConfigurableApplicationContext context) {
this.context = context;
}
@Override
public boolean reportException(Throwable failure) {
if (failure instanceof ConnectorStartFailedException) {
System.out.println("\nConnectorStartFailedException:连接失败异常,端口号:"
+ ((ConnectorStartFailedException) failure).getPort() +
",上下文状态:" + (context.isActive() ? "active" : "closed"));
}
return false;
}
}
之后还要在resources/META-INF/
目录下新建spring.factories:
在spring.factories中配置如下:
org.springframework.boot.SpringBootExceptionReporter=com.wenjie.sb2.except.MyExceptionReporter
再复现一次端口占用的错误,控制台输出如下:
ConnectorStartFailedException:连接失败异常,端口号:8080,上下文状态:active
``