0


Spring Boot 异常报告器解析

基于Spring Boot 3.1.0 系列文章

  1. Spring Boot 源码阅读初始化环境搭建
  2. Spring Boot 框架整体启动流程详解
  3. Spring Boot 系统初始化器详解
  4. Spring Boot 监听器详解
  5. Spring Boot banner详解
  6. Spring Boot 属性配置解析
  7. Spring Boot 属性加载原理解析
  8. Spring Boot 异常报告器解析

创建自定义异常报告器

FailureAnalysis 是Spring Boot 启动时将异常转化为可读消息的一种方法,系统自定义了很多异常报告器,通过接口也可以自定义异常报告器。

创建一个异常类:

publicclassMyExceptionextendsRuntimeException{}

创建一个FailureAnalyzer:

publicclassMyFailureAnalyzerextendsAbstractFailureAnalyzer<MyException>{@OverrideprotectedFailureAnalysisanalyze(Throwable rootFailure,MyException cause){String des ="发生自定义异常";String action ="由于自定义了一个异常";returnnewFailureAnalysis(des, action, rootFailure);}}

需要在Spring Boot 启动的时候抛出异常,为了测试,我们在上下文准备的时候抛出自定义异常,添加到demo中的MyApplicationRunListener中。

publicvoidcontextPrepared(ConfigurableApplicationContext context){System.out.println("在创建和准备ApplicationContext之后,但在加载源之前调用");thrownewMyException();}

启动后就会打印出我们的自定义异常报告器内容:

***************************
APPLICATION FAILED TO START
***************************

Description:

发生自定义异常

Action:

由于自定义了一个异常

原理分析

在之前的文章《Spring Boot 框架整体启动流程详解》,有讲到过Spring Boot 对异常的处理,如下是Spring Boot 启动时的代码:

publicConfigurableApplicationContextrun(String... args){long startTime =System.nanoTime();DefaultBootstrapContext bootstrapContext =createBootstrapContext();ConfigurableApplicationContext context =null;configureHeadlessProperty();SpringApplicationRunListeners listeners =getRunListeners(args);
        listeners.starting(bootstrapContext,this.mainApplicationClass);try{ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);ConfigurableEnvironment environment =prepareEnvironment(listeners, bootstrapContext, applicationArguments);Banner printedBanner =printBanner(environment);
            context =createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);Duration timeTakenToStartup =Duration.ofNanos(System.nanoTime()- startTime);if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}
            listeners.started(context, timeTakenToStartup);callRunners(context, applicationArguments);}catch(Throwable ex){if(ex instanceofAbandonedRunException){throw ex;}handleRunFailure(context, ex, listeners);thrownewIllegalStateException(ex);}try{if(context.isRunning()){Duration timeTakenToReady =Duration.ofNanos(System.nanoTime()- startTime);
                listeners.ready(context, timeTakenToReady);}}catch(Throwable ex){if(ex instanceofAbandonedRunException){throw ex;}handleRunFailure(context, ex,null);thrownewIllegalStateException(ex);}return context;}

通过两个try…catch…包裹,在catch 中判断异常是否是AbandonedRunException类型,是直接抛出异常,否则的话进入

handleRunFailure

中。

AbandonedRunException 异常 在 Spring Boot 处理AOT相关优化的时候会抛出

privatevoidhandleRunFailure(ConfigurableApplicationContext context,Throwable exception,SpringApplicationRunListeners listeners){try{try{//处理exitCodehandleExitCode(context, exception);if(listeners !=null){//发送启动失败事件
                listeners.failed(context, exception);}}finally{//获取报告处理器,并处理错误reportFailure(getExceptionReporters(context), exception);if(context !=null){//关闭上下文
                context.close();//移除关闭钩子
                shutdownHook.deregisterFailedApplicationContext(context);}}}catch(Exception ex){
        logger.warn("Unable to close ApplicationContext", ex);}//重新抛出异常ReflectionUtils.rethrowRuntimeException(exception);}

exitCode是一个整数值,默认返回0,Spring Boot会将该exitCode传递给System.exit()以作为状态码返回,如下是IDEA中停止Spring Boot 返回的退出码:

进程已结束,退出代码130

handleExitCode

进入

handleExitCode

,看下是如何处理的:

privatevoidhandleExitCode(ConfigurableApplicationContext context,Throwable exception){int exitCode =getExitCodeFromException(context, exception);//exitCode非0if(exitCode !=0){if(context !=null){//发送ExitCodeEvent事件
            context.publishEvent(newExitCodeEvent(context, exitCode));}//获取当前线程的SpringBootExceptionHandler,SpringBootExceptionHandler用来处理未捕获的异常,实现了UncaughtExceptionHandler接口
          handler =getSpringBootExceptionHandler();if(handler !=null){//添加exitCode到SpringBootExceptionHandler 中
            handler.registerExitCode(exitCode);}}}privateintgetExitCodeFromException(ConfigurableApplicationContext context,Throwable exception){//从ExitCodeExceptionMapper实现中获取exitCodeint exitCode =getExitCodeFromMappedException(context, exception);if(exitCode ==0){//尝试从ExitCodeGenerator实现获取exitCode
        exitCode =getExitCodeFromExitCodeGeneratorException(exception);}return exitCode;}privateintgetExitCodeFromMappedException(ConfigurableApplicationContext context,Throwable exception){//判断上下文是否是活动状态,上下文至少刷新过一次,不是就返回0if(context ==null||!context.isActive()){return0;}//用于维护ExitCodeGenerator有序集合的组合器,ExitCodeGenerator 是一个接口,用于获取exitCodeExitCodeGenerators generators =newExitCodeGenerators();//获取ExitCodeExceptionMapper类型的BeanCollection<ExitCodeExceptionMapper> beans = context.getBeansOfType(ExitCodeExceptionMapper.class).values();//将异常和bean包装成MappedExitCodeGenerator,排序后保存,MappedExitCodeGenerator是ExitCodeGenerator 的一个实现
    generators.addAll(exception, beans);//会循环ExitCodeGenerators 中的ExitCodeGenerator,ExitCodeGenerator会去获取ExitCodeExceptionMapper的实现,如果有一个exitCode非0则马上返回,否则返回0return generators.getExitCode();}privateintgetExitCodeFromExitCodeGeneratorException(Throwable exception){//没有异常if(exception ==null){return0;}//异常类有实现了ExitCodeGenerator 接口if(exception instanceofExitCodeGenerator generator){return generator.getExitCode();}//继续寻找returngetExitCodeFromExitCodeGeneratorException(exception.getCause());}SpringBootExceptionHandlergetSpringBootExceptionHandler(){//当前线程是主线程if(isMainThread(Thread.currentThread())){//获取当前线程的SpringBootExceptionHandlerreturnSpringBootExceptionHandler.forCurrentThread();}returnnull;}

listeners.failed

在处理完exitCode后,继续执行

listeners.failed(context, exception)

,这里就跟以前一样,循环SpringApplicationRunListener实现

reportFailure

Spring Boot 首先从

spring.factories

获取所有的

SpringBootExceptionReporter

实现,

FailureAnalyzers

是其唯一实现,其用于加载和执行

FailureAnalyzer

reportFailure 循环执行获取的SpringBootExceptionReporter,如果发送异常成功,则会向之前的

SpringBootExceptionHandler

中记录,表示该异常已经捕获处理

privatevoidreportFailure(Collection<SpringBootExceptionReporter> exceptionReporters,Throwable failure){try{for(SpringBootExceptionReporter reporter : exceptionReporters){//如果异常发送成功if(reporter.reportException(failure)){//记录异常registerLoggedException(failure);return;}}}catch(Throwable ex){// 如果上述操作发生异常,还是会继续执行}//记录error级别日志if(logger.isErrorEnabled()){
        logger.error("Application run failed", failure);registerLoggedException(failure);}}

reporter.reportException

在reportFailure中,通过reporter.reportException(failure)判断异常是否发送成功,进入代码,由于该Demo 只有一个FailureAnalyzers实现,所以进入到FailureAnalyzers的reportException中:

publicbooleanreportException(Throwable failure){//循环调用加载的FailureAnalyzer实现的analyze方法FailureAnalysis analysis =analyze(failure,this.analyzers);//加载FailureAnalysisReporter实现,组装具体错误信息,并打印日志returnreport(analysis);}
this.analyzers

FailureAnalyzers

创建的时候已经将

FailureAnalyzer

实现从

spring.factories

中加载
下面的代码将循环调用加载的FailureAnalyzer实现的analyze方法,返回一个包装了

异常描述、发生异常的动作、原始异常

信息的对象

privateFailureAnalysisanalyze(Throwable failure,List<FailureAnalyzer> analyzers){for(FailureAnalyzer analyzer : analyzers){try{FailureAnalysis analysis = analyzer.analyze(failure);if(analysis !=null){return analysis;}}catch(Throwable ex){
            logger.trace(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);}}returnnull;}

此处Spring Boot 建议自定义的FailureAnalyzer 通过继承

AbstractFailureAnalyzer

来实现,Spring Boot 自带的FailureAnalyzer确实也是这样的,但是你也可以直接实现FailureAnalyzer 接口。AbstractFailureAnalyzer中会筛选出需要关注的异常,而直接实现FailureAnalyzer 接口,需要自行在方法中处理。
随后将返回的FailureAnalysis实现通过FailureAnalysisReporter组装打印到客户端

privatebooleanreport(FailureAnalysis analysis){//FailureAnalysisReporter也是从spring.factories中加载,可见也可以自定义List<FailureAnalysisReporter> reporters =this.springFactoriesLoader.load(FailureAnalysisReporter.class);if(analysis ==null|| reporters.isEmpty()){returnfalse;}for(FailureAnalysisReporter reporter : reporters){
        reporter.report(analysis);}returntrue;}

在该Demo中,只有一个FailureAnalysisReporter实例

LoggingFailureAnalysisReporter
publicvoidreport(FailureAnalysis failureAnalysis){//如果是debug级别,则会打印堆栈信息if(logger.isDebugEnabled()){
        logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());}//如果是error级别,还会打印组装好的错误信息if(logger.isErrorEnabled()){
        logger.error(buildMessage(failureAnalysis));}}privateStringbuildMessage(FailureAnalysis failureAnalysis){StringBuilder builder =newStringBuilder();
    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();}

关闭上下文、移除钩子

context.close() 如果上下文不为空,则关闭上下文,并且移除关闭钩子。
shutdownHook.deregisterFailedApplicationContext(context) 用来将之前在SpringApplicationShutdownHook 钩子中注册的上下文移除。
SpringApplicationShutdownHook 是Spring Boot 定义的关闭钩子,用来优雅关机。

总结

在这里插入图片描述

标签: spring boot java spring

本文转载自: https://blog.csdn.net/weixin_40972073/article/details/131242002
版权归原作者 阿提说说 所有, 如有侵权,请联系我们删除。

“Spring Boot 异常报告器解析”的评论:

还没有评论