2.7、Spring Boot 异常处理体系

本Spring Boot版本是2.2.2

1、流程

1.1、 初始化流程

在run 方法中,将所有的方法都放在了try块中,而在catch 块中处理所有的异常和错误(Throwable )

(1) 创建 SpringBootExceptionReporter 实例
从spring.factories中获取 SpringBootExceptionReporter 类型的类的全类名,实例化这些类

(2)创建 FailureAnlyzer 实例
从从spring.factories中获取 FailureAnlyzer 类型的类的全类名,实例化这些类并放入 SpringBootExceptionReporter 实例的List实例变量中

在这里插入图片描述

1.2、调用流程

2、源码

public ConfigurableApplicationContext run(String... args) {
		...
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		...
		try {
			...
			// 从spring.factories 文件中获取并创建所有 SpringBootExceptionReporter 类型实例
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
		...
		}
		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;
	}

2.1、创建 SpringBootExceptionReporter 实例

1、从spring.factories 中解析出全类名(SpringBootExceptionReporter类型的类的)

之前分析过,这里略…

2、创建这些类的实例,其中有FailureAnalyzers(目前SpringBoot中SpringBootExceptionReporter接口只有这个一个实现类),FailureAnalyzers构造方法如下:

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);
}

FailureAnalyzers 构造方法做了两件事情

(2.1)解析,创建,排序 ,即从spring.factories中解析所有 FailureAnalyzer 类型的类全名,并创建这些类的实例

private List<FailureAnalyzer> loadFailureAnalyzers(ClassLoader classLoader) {
	List<String> analyzerNames = SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, classLoader);
	List<FailureAnalyzer> analyzers = new ArrayList<>();
	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(LogMessage.format("Failed to load %s", analyzerName), ex);
		}
	}
	AnnotationAwareOrderComparator.sort(analyzers);
	return analyzers;
}

笔者环境解析结果

analyzers = {ArrayList@3363}  size = 18
 0 = {BeanCurrentlyInCreationFailureAnalyzer@2918} 
 1 = {BeanDefinitionOverrideFailureAnalyzer@3402} 
 2 = {BeanNotOfRequiredTypeFailureAnalyzer@3403} 
 3 = {BindFailureAnalyzer@3404} 
 4 = {BindValidationFailureAnalyzer@3405} 
 5 = {UnboundConfigurationPropertyFailureAnalyzer@3406} 
 6 = {ConnectorStartFailureAnalyzer@3407} 
 7 = {NoSuchMethodFailureAnalyzer@3408} 
 8 = {NoUniqueBeanDefinitionFailureAnalyzer@3398} 
 9 = {PortInUseFailureAnalyzer@3409} 
 10 = {ValidationExceptionFailureAnalyzer@3410} 
 11 = {InvalidConfigurationPropertyNameFailureAnalyzer@3411} 
 12 = {InvalidConfigurationPropertyValueFailureAnalyzer@3412} 
 13 = {NoSuchBeanDefinitionFailureAnalyzer@3413} 
 14 = {FlywayMigrationScriptMissingFailureAnalyzer@3414} 
 15 = {DataSourceBeanCreationFailureAnalyzer@3415} 
 16 = {HikariDriverConfigurationFailureAnalyzer@3416} 
 17 = {NonUniqueSessionRepositoryFailureAnalyzer@3417} 

这些类都是 FailureAnalyzer 接口的实现类,我们来看看 FailureAnalyzer 接口

public interface FailureAnalyzer {
/**
 * 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 ,FailureAnalysis 用来封装失败信息的,下面是FailureAnalysis 类的定义:

public class FailureAnalysis {
	private final String description;// 错误描述
	private final String action;// 处理失败的操作
	private final Throwable cause;// 失败原因
}

下面是使用同一端口第二次启动应用的报错信息,大家看看 Description 和 Action 的描述感受一下

Description:
	Web server failed to start. Port 8008 was already in use.
Action:
	Identify and stop the process that's listening on port 8008 or configure this application to listen on another 
	port.

(2.2)为BeanFactoryAware 或 EnvironmentAware类型的 FailureAnalyzer 实例,注入BeanFactory 或 Environment

private void prepareFailureAnalyzers(List<FailureAnalyzer> analyzers, ConfigurableApplicationContext context) {
	for (FailureAnalyzer analyzer : analyzers) {
		prepareAnalyzer(context, analyzer);
	}
}

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());
	}
}

FailureAnalyzer 类型的实例有:

1、BeanFactoryAware
	NoUniqueBeanDefinitionFailureAnalyzer
	NoSuchBeanDefinitionFailureAnalyzer
2、EnvironmentAware
	InvalidConfigurationPropertyValueFailureAnalyzer
	DataSourceBeanCreationFailureAnalyzer

2.2、处理异常

当程序发生异常时,在 catch 块中处理这个异常

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
			Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
	try {
		try {
			// 1、
			handleExitCode(context, exception);
			if (listeners != null) {
				// 2、
				listeners.failed(context, exception);
			}
		}
		finally {
			// 3、
			reportFailure(exceptionReporters, exception);
			if (context != null) {
				// 4、
				context.close();
			}
		}
	}
	catch (Exception ex) {
		logger.warn("Unable to close ApplicationContext", ex);
	}
	ReflectionUtils.rethrowRuntimeException(exception);
}

发生端口占用异常时:

exception = {PortInUseException@5268} "org.springframework.boot.web.server.PortInUseException: 
																					Port 8008 is already in use"
 $assertionsDisabled = true
 cause = null
 CAUSE_CAPTION = "Caused by: "
 detailMessage = "Port 8008 is already in use"
 EMPTY_THROWABLE_ARRAY = {Throwable[0]@5295} 
 Exception.serialVersionUID = -3387516993124229948
 NULL_CAUSE_MESSAGE = "Cannot suppress a null exception."
 port = 8008
 SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted"
 serialVersionUID = -7034897190745766939
 stackTrace = {StackTraceElement[10]@5300} 
 SUPPRESSED_CAPTION = "Suppressed: "
 SUPPRESSED_SENTINEL = {Collections$UnmodifiableRandomAccessList@5302}  size = 0
 suppressedExceptions = {Collections$UnmodifiableRandomAccessList@5302}  size = 0
 Throwable.serialVersionUID = -3042686055658047285
 UNASSIGNED_STACK = {StackTraceElement[0]@5298} 

(1)、


/********** org.springframework.boot.SpringApplication ******************/
private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
	int exitCode = getExitCodeFromException(context, exception);
	if (exitCode != 0) {
		if (context != null) {
			context.publishEvent(new ExitCodeEvent(context, exitCode));
		}
		SpringBootExceptionHandler handler = getSpringBootExceptionHandler();
		if (handler != null) {
			handler.registerExitCode(exitCode);
		}
	}
}
private int getExitCodeFromException(ConfigurableApplicationContext context, Throwable exception) {
	int exitCode = getExitCodeFromMappedException(context, exception);
	if (exitCode == 0) {
		exitCode = getExitCodeFromExitCodeGeneratorException(exception);
	}
	return exitCode;
}
private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
	if (context == null || !context.isActive()) {
		return 0;
	}
	ExitCodeGenerators generators = new ExitCodeGenerators();
	
	Collection<ExitCodeExceptionMapper> beans // 空的
										= context.getBeansOfType(ExitCodeExceptionMapper.class).values();
	generators.addAll(exception, beans);// exception 就是 PortInUseException实例
	return generators.getExitCode();
}
private int getExitCodeFromExitCodeGeneratorException(Throwable exception) {
	if (exception == null) {
		return 0;
	}
	if (exception instanceof ExitCodeGenerator) {
		return ((ExitCodeGenerator) exception).getExitCode();
	}
	return getExitCodeFromExitCodeGeneratorException(exception.getCause());
}

/********** org.springframework.boot.ExitCodeGenerators ******************/
void addAll(Throwable exception, Iterable<? extends ExitCodeExceptionMapper> mappers) {
	Assert.notNull(exception, "Exception must not be null");
	Assert.notNull(mappers, "Mappers must not be null");
	for (ExitCodeExceptionMapper mapper : mappers) {
		add(exception, mapper);
	}
}
void add(Throwable exception, ExitCodeExceptionMapper mapper) {
	Assert.notNull(exception, "Exception must not be null");
	Assert.notNull(mapper, "Mapper must not be null");
	add(new MappedExitCodeGenerator(exception, mapper));
}
void add(ExitCodeGenerator generator) {
	Assert.notNull(generator, "Generator must not be null");
	this.generators.add(generator);
}

int getExitCode() {
	int exitCode = 0;
	for (ExitCodeGenerator generator : this.generators) {
		try {
			int value = generator.getExitCode();
			if (value > 0 && value > exitCode || value < 0 && value < exitCode) {
				exitCode = value;
			}
		}
		catch (Exception ex) {
			exitCode = (exitCode != 0) ? exitCode : 1;
			ex.printStackTrace();
		}
	}
	return exitCode;
}

(2)、

(3)、报告异常

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);
	}
}
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.debug(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);
		}
	}
	return null;
}
protected Class<? extends T> getCauseType() {
	return (Class<? extends T>) ResolvableType.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric();
}

(4)、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java硕哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值