文章目录

前言

本节就来简单看看SpringBoot的FailureAnalyzer调用流程,学习下SpringBoot处理异常的手段之一。(本节以启动异常为例)

FailureAnalyzers介绍

FailureAnalyzers属性

在讲FailureAnalyzer之前,必须先讲一讲FailureAnalyzers,因为FailureAnalyzer就是在FailureAnalyzers中构造的,下面先来看看FailureAnalyzers的属性:

FailureAnalyzers.png

  • 毫无疑问,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的配置:
image.png

  • 这一点在初始化器那里也提到过,这里就不再赘述了。
  • 实际上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子类中,我们能看到几个比较眼熟的东西:

image.png

  • 我们平时端口重复、找不到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文件中读取配置的:
image.png

image.png

总计17个:
image.png

FailureAnalysis简介

看类图一目了然:
image.png

  • 作用就是保存FailureAnalyzer分析完后的异常描述、动作、对象。

以上都只是简单介绍,下面人为制造一些错误,来看看整个流程是什么样的。

跟进源码

我这里就拿端口重复的做例子,拿什么占用端口这随便你发挥,你把端口封掉也8是问题~

下面,我就以应用端口已经被占用为前提(请确保不会产生其它错误),跟进源码。

在文末还会补充一张源码追踪的流程图。

启动应用后,会来到SpringApplication#run的如下位置,我们就从这里开始跟进:
image.png

注意,在进入这个断点之前,控制台就已经输出了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实现:

image.png

  • 其中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对象如下,相信应该有不少人对其中的文字都有点眼熟:

image.png

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的实现默认同样只有一个:
spring.factories.png

跟进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

至此,端口重复占用的异常就处理完毕了。

流程图

回顾下整个流程,花了张流程图如下(只显示几个核心步骤):

异常流程.png

扩展

自定义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:

image.png

在spring.factories中配置如下:

org.springframework.boot.SpringBootExceptionReporter=com.wenjie.sb2.except.MyExceptionReporter

再复现一次端口占用的错误,控制台输出如下:

ConnectorStartFailedException:连接失败异常,端口号:8080,上下文状态:active

``