当前位置 : 主页 > 编程语言 > java >

SpringBoot——异常报告器解析

来源:互联网 收集:自由互联 发布时间:2023-02-04
异常报告器类介绍 接口规范 @FunctionalInterfacepublic interface SpringBootExceptionReporter {/** * Report a startup failure to the user. * @param failure the source failure * @return {@code true} if the failure was reported or {@

异常报告器类介绍

接口规范

@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.FailureAnalyzers

SpringBoot通过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事件
public void failed(ConfigurableApplicationContext context, Throwable exception) { for (SpringApplicationRunListener listener : this.listeners) { callFailedListener(listener, context, exception); } } 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 + ")"); } } } public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { @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(exceptionReporters, exception)

  • SpringBootExceptionReporter实现类调用reportException方法
  • 成功处理的话,记录已处理异常
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); } }

context.close()

  • 更改应用上下文状态
  • 销毁单例bean
  • 将beanFactory置为空
  • 关闭web容器(web环境)
  • 移除shutdownHook
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { @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 } } } } protected void doClose() { // Check whether an actual close attempt is necessary... if (this.active.get() && this.closed.compareAndSet(false, true)) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this); } LiveBeansView.unregisterApplicationContext(this); try { // Publish shutdown event. // 发布容器关闭事件 publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. if (this.lifecycleProcessor != null) { try { this.lifecycleProcessor.onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); } } // Destroy all cached singletons in the context's BeanFactory. // 销毁所有的单例bean destroyBeans(); // Close the state of this context itself. // 关闭容器 closeBeanFactory(); // Let subclasses do some final clean-up if they wish... // 调用子类的重写方法,关闭web服务器 onClose(); // Reset local application listeners to pre-refresh state. if (this.earlyApplicationListeners != null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // Switch to inactive. this.active.set(false); } } }

ReflectionUtils.rethrowRuntimeException(exception)

  • 重新抛出异常
public static void rethrowRuntimeException(Throwable ex) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } if (ex instanceof Error) { throw (Error) ex; } throw new UndeclaredThrowableException(ex); }

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中的内容。
public static void main(String[] args) { // register shutdown hook Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Do something in Shutdown Hook"))); // sleep for some time try { for (int i=0; i<3; i++) { System.out.println("Count: " + i + "..."); TimeUnit.MILLISECONDS.sleep(1000); } List nullList = new ArrayList<>(); System.out.println("Trying to print null list's first element: " + nullList.get(0).toString()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ready to exit."); System.exit(0); }

结果如下:

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

上一篇:SpringBoot——配置类解析
下一篇:没有了
网友评论