基于Spring Boot 3.1.0 系列文章
- Spring Boot 源码阅读初始化环境搭建
- Spring Boot 框架整体启动流程详解
- Spring Boot 系统初始化器详解
- Spring Boot 监听器详解
- Spring Boot banner详解
- Spring Boot 属性配置解析
- Spring Boot 属性加载原理解析
- 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 定义的关闭钩子,用来优雅关机。
总结
版权归原作者 阿提说说 所有, 如有侵权,请联系我们删除。