异常报告器类介绍
接口规范
@FunctionalInterface public interface SpringBootExceptionReporter { /** * Report a startup failure to the user. * @param failure the source failure * @return {@code true} if the failure was reported or {@code false} if default * reporting should occur. */ boolean reportException(Throwable failure); }SpringBootExceptionReporter初始化情况
public ConfigurableApplicationContext run(String... args) { ...... //初始化构造一个SpringBootExceptionReporter集合 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); try { ... //给exceptionReporters集合填充具体的对象 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); ... } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } ... return context; }通过SpringFactoriesLoader加载
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)); // 也是通过SpringFactoriesLoader技术来加载,见下图,默认就一个,这边实例化会调用其构造函数 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } # Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzersSpringBoot通过SPI技术为SpringBootExceptionReporter接口指定了唯一的实现FailureAnalyzers
final class FailureAnalyzers implements SpringBootExceptionReporter { private final ClassLoader classLoader; private final List<FailureAnalyzer> analyzers; 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); prepareFailureAnalyzers(this.analyzers, context); } 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("Failed to load " + analyzerName, ex); } } AnnotationAwareOrderComparator.sort(analyzers); return analyzers; } 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()); } } }再看List<FailureAnalyzer> analyzers集合中的FailureAnalyzer
@FunctionalInterface 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 FailureAnalysis analyze(Throwable failure); } public class FailureAnalysis { //错误描述 private final String description; //引起错误的动作 private final String action; //错误本身 private final Throwable cause; }下面我们看下FailureAnalyzers实现的SpringBootExceptionReporter的异常报告方法
@Override public boolean reportException(Throwable failure) { FailureAnalysis analysis = analyze(failure, this.analyzers); return report(analysis, this.classLoader); } private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) { for (FailureAnalyzer analyzer : analyzers) { try { // 依次遍历每个analyzer分析,有结果就返回,这边走到AbstractFailureAnalyzer的analyze方法 FailureAnalysis analysis = analyzer.analyze(failure); if (analysis != null) { return analysis; } } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("FailureAnalyzer " + analyzer + " failed", ex); } } } return null; }AbstractFailureAnalyzer实现了FailureAnalyzer接口
public abstract class AbstractFailureAnalyzer<T extends Throwable> implements FailureAnalyzer { @Override public FailureAnalysis analyze(Throwable failure) { // 会先走到这里 T cause = findCause(failure, getCauseType()); if (cause != null) { // 找到子类感兴趣的异常后,调用具体实现类的分析方法 return analyze(failure, cause); } return null; } protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause); // 这个就是解析我的泛型参数,表明我对哪个异常感兴趣 protected Class<? extends T> getCauseType() { return (Class<? extends T>) ResolvableType.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric(); } protected final <E extends Throwable> E findCause(Throwable failure, Class<E> type) { // 依次遍历异常堆栈,如果有对当前异常感兴趣,就返回,否则返回null while (failure != null) { if (type.isInstance(failure)) { return (E) failure; } failure = failure.getCause(); } return null; } }再来看看report(analysis, this.classLoader)方法
// 分析出结果之后,调用report方法之后报告异常 private boolean report(FailureAnalysis analysis, ClassLoader classLoader) { // FailureAnalysisReporter这个实现类只有一个,是LoggingFailureAnalysisReporter List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class, classLoader); if (analysis == null || reporters.isEmpty()) { return false; } for (FailureAnalysisReporter reporter : reporters) { // 输出异常报告 reporter.report(analysis); } return true; } public final class LoggingFailureAnalysisReporter implements FailureAnalysisReporter { private static final Log logger = LogFactory.getLog(LoggingFailureAnalysisReporter.class); @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(); } }SpringBoot异常处理流程
public ConfigurableApplicationContext run(String... args) { ...... Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); ...... try { ...... exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); ...... } catch (Throwable ex) { //对启动过程中的失败进行处理 handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } ...... return context; }点进去handleRunFailure(context, ex, exceptionReporters, listeners)
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); }handleRunFailure(context, ex, exceptionReporters, listeners)逻辑
handleExitCode(context, exception)
private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) { // 获取框架内对这个异常定义的exitCode,代表退出状态码,为0代表正常退出,不为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) { //记录exitCode handler.registerExitCode(exitCode); } } }listeners.failed(context, exception)
- 发布ApplicationFailedEvent事件
reportFailure(exceptionReporters, exception)
- SpringBootExceptionReporter实现类调用reportException方法
- 成功处理的话,记录已处理异常
context.close()
- 更改应用上下文状态
- 销毁单例bean
- 将beanFactory置为空
- 关闭web容器(web环境)
- 移除shutdownHook
ReflectionUtils.rethrowRuntimeException(exception)
- 重新抛出异常
shutdownHook介绍
- 作用:JVM退出时执行的业务逻辑
- 添加:Runtime.getRuntime().addShutdownHook()
- 移除:Runtime.getRuntime().removeShutdownHook(this.shutdownHook)
背景
在开发中,遇到这种情况,多个线程同时工作,突然一个线程遇到了fetal的错误,需要立即终止程序,等人工排查解决了问题之后重新启动。但是这样会有一个问题,程序终止时,其他线程可能正在进行重要操作,比如发一个message到另一个模块,并更新数据库状态。突然终止,可能会让这个操作只完成一半,从而导致数据不一致。
解决方案是:参考数据库Transaction原子性的概念,将这一系列重要操作看作一个整体,要么全部完成,要么全部不完成。为方便表述,我们把这一系列重要操作记为操作X。
当程序即将退出时,查看当前是否有操作X在执行中,如果有,等待其完成然后退出。且期间不再接受新的操作X。如果操作X执行之间过长,终止并回滚所有状态。
如果没有,则可以立即退出。
在程序退出的时候,做一些Check,保证已经开始的操作X的原子性,这里就用到了Runtime.ShutdownHook。
什么是Shutdown Hook
Shutdown hook是一个initialized but unstarted thread。当JVM开始执行shutdown sequence时,会并发运行所有registered Shutdown Hook。这时,在Shutdown Hook这个线程里定义的操作便会开始执行。
需要注意的是,在Shutdown Hook里执行的操作应当是不太耗时的。因为在用户注销或者操作系统关机导致的JVM shutdown的例子中,系统只会预留有限的时间给未完成的工作,超时之后还是会强制关闭。
什么时候会调用Shutdown Hook
程序正常停止
- Reach the end of program
- System.exit
程序异常退出
- NPE
- OutOfMemory
受到外界影响停止
- Ctrl+C
- 用户注销或者关机
如何使用Shutdown Hook
调用java.lang.Runtime这个类的addShutdownHook(Thread hook)方法即可注册一个Shutdown Hook,然后在Thread中定义需要在system exit时进行的操作。如下:
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Do something in Shutdown Hook")));测试例子
- 首先,注册了一个Shutdown Hook。
- 然后,系统Sleep 3秒,模拟进行某些操作。
- 然后,调用一个空的List,抛出异常,准备结束程序。
- 在程序将要结束的时候,执行Shutdown Hook中的内容。
结果如下:
Count: 0... Count: 1... Count: 2... Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:653) at java.util.ArrayList.get(ArrayList.java:429) at HookTest.main(HookTest.java:18) Do something in Shutdown Hook Process finished with exit code 1需要注意的点
- 当System.exit之后,当Shutdown Hook开始执行时,其他的线程还是会继续执行。
- 应当保证Shutdown Hook的线程安全。
- 在使用多个Shutdown Hook时一定要特别小心,保证其调用的服务不会被其他Hook影响。否则会出现当前Hook所依赖的服务被另外一个Hook终止了的情况。
参考: https://my.oschina.net/liwanghong/blog/3167733
https://www.cnblogs.com/maxstack/p/9112711.html
https://www.cnblogs.com/tracer-dhy/p/10041643.html