Spring Boot 异常报告器解析

news2024/9/27 12:14:57

基于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 启动时将异常转化为可读消息的一种方法,系统自定义了很多异常报告器,通过接口也可以自定义异常报告器。

创建一个异常类:

public class MyException extends RuntimeException{
}

创建一个FailureAnalyzer:

public class MyFailureAnalyzer extends AbstractFailureAnalyzer<MyException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, MyException cause) {
        String des = "发生自定义异常";
        String action = "由于自定义了一个异常";
        return new FailureAnalysis(des, action, rootFailure);
    }
}

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

public void contextPrepared(ConfigurableApplicationContext context) {
    System.out.println("在创建和准备ApplicationContext之后,但在加载源之前调用");
    throw new MyException();
}

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

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

Description:

发生自定义异常

Action:

由于自定义了一个异常

原理分析

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

public ConfigurableApplicationContext run(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 = new DefaultApplicationArguments(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) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
			listeners.started(context, timeTakenToStartup);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			if (ex instanceof AbandonedRunException) {
				throw ex;
			}
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			if (context.isRunning()) {
				Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
				listeners.ready(context, timeTakenToReady);
			}
		}
		catch (Throwable ex) {
			if (ex instanceof AbandonedRunException) {
				throw ex;
			}
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

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

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

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
		SpringApplicationRunListeners listeners) {
	try {
		try {
		//处理exitCode
			handleExitCode(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,看下是如何处理的:

private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
	int exitCode = getExitCodeFromException(context, exception);
	//exitCode非0
	if (exitCode != 0) {
		if (context != null) {
		//发送ExitCodeEvent事件
			context.publishEvent(new ExitCodeEvent(context, exitCode));
		}
		//获取当前线程的SpringBootExceptionHandler,SpringBootExceptionHandler用来处理未捕获的异常,实现了UncaughtExceptionHandler接口
		  handler = getSpringBootExceptionHandler();
		if (handler != null) {
		//添加exitCode到SpringBootExceptionHandler 中
			handler.registerExitCode(exitCode);
		}
	}
}

private int getExitCodeFromException(ConfigurableApplicationContext context, Throwable exception) {
//从ExitCodeExceptionMapper实现中获取exitCode
	int exitCode = getExitCodeFromMappedException(context, exception);
	if (exitCode == 0) {
	//尝试从ExitCodeGenerator实现获取exitCode
		exitCode = getExitCodeFromExitCodeGeneratorException(exception);
	}
	return exitCode;
}

private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
//判断上下文是否是活动状态,上下文至少刷新过一次,不是就返回0
	if (context == null || !context.isActive()) {
		return 0;
	}
	//用于维护ExitCodeGenerator有序集合的组合器,ExitCodeGenerator 是一个接口,用于获取exitCode
	ExitCodeGenerators generators = new ExitCodeGenerators();
	//获取ExitCodeExceptionMapper类型的Bean
	Collection<ExitCodeExceptionMapper> beans = context.getBeansOfType(ExitCodeExceptionMapper.class).values();
	//将异常和bean包装成MappedExitCodeGenerator,排序后保存,MappedExitCodeGenerator是ExitCodeGenerator 的一个实现
	generators.addAll(exception, beans);
	//会循环ExitCodeGenerators 中的ExitCodeGenerator,ExitCodeGenerator会去获取ExitCodeExceptionMapper的实现,如果有一个exitCode非0则马上返回,否则返回0
	return generators.getExitCode();
}

private int getExitCodeFromExitCodeGeneratorException(Throwable exception) {
//没有异常
	if (exception == null) {
		return 0;
	}
	//异常类有实现了ExitCodeGenerator 接口
	if (exception instanceof ExitCodeGenerator generator) {
		return generator.getExitCode();
	}
	//继续寻找
	return getExitCodeFromExitCodeGeneratorException(exception.getCause());
}

SpringBootExceptionHandler getSpringBootExceptionHandler() {
//当前线程是主线程
	if (isMainThread(Thread.currentThread())) {
	//获取当前线程的SpringBootExceptionHandler
		return SpringBootExceptionHandler.forCurrentThread();
	}
	return null;
}

listeners.failed

在处理完exitCode后,继续执行listeners.failed(context, exception),这里就跟以前一样,循环SpringApplicationRunListener实现

reportFailure

Spring Boot 首先从spring.factories获取所有的SpringBootExceptionReporter实现,FailureAnalyzers是其唯一实现,其用于加载和执行FailureAnalyzer
reportFailure 循环执行获取的SpringBootExceptionReporter,如果发送异常成功,则会向之前的SpringBootExceptionHandler中记录,表示该异常已经捕获处理

private void reportFailure(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中:

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

this.analyzersFailureAnalyzers创建的时候已经将FailureAnalyzer实现从spring.factories中加载
下面的代码将循环调用加载的FailureAnalyzer实现的analyze方法,返回一个包装了异常描述、发生异常的动作、原始异常 信息的对象

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.trace(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);
		}
	}
	return null;
}

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

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

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

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

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

关闭上下文、移除钩子

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

总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么查投票人画展投票链接怎么做微信链接投票

近些年来&#xff0c;第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放&#xff0c;更多人选择微信投票小程序平台&#xff0c;因为它有非常大的优势。 1.它比起微信公众号自带的投票系统、传统的H5投票系统有可以图…

C语言入门万字笔记

C语言一经出现就以其功能丰富、表达能力强、灵活方便、应用面广等特点迅速在全世界普及和推广。C语言不但执行效率高而且可移植性好&#xff0c;可以用来开发应用软件、驱动、操作系统等。C语言也是其它众多高级语言的鼻祖语言&#xff0c;所以说学习C语言是进入编程世界的必修…

机器学习、计算机视觉和深度学习

机器学习、计算机视觉和深度学习 1 什么是机器学习&#xff1f;2 机器学习的类型3 什么是计算机视觉&#xff1f;4 计算机视觉的机器学习应用5 总结参考 这篇博客将简要介绍&#xff1a;机器学习和用于计算机视觉的机器学习。 想象一下&#xff1a;你可以使用人脸检测算法在图…

【后端】SSM框架下REST风格代码注释详解

前言 最近学习了一下SSM&#xff0c;不得不说&#xff0c;spring不用注解真的是天打雷劈&#xff0c;就那个bean真的就是折磨人。 下面是我总结的spring注解。 Value 此注解可以用来获取导入的jdbc.properties文件的值。 Value("${jdbc.driver}")private String…

【C++】C++11新特性重点:可变参数+lambda

C11新特性第二篇重点 文章目录 上一篇的补充一、可变参数模板二、lambda函数总结 前言 上一篇我们重点讲解了右值引用移动语义&#xff0c;关于移动构造和移动赋值还有一些需要补充的知识&#xff1a; 如果你没有自己实现移动构造函数&#xff0c;且没有实现析构函数 、拷贝构…

dubbo 3.2.0 的filterChain 简要分析

dubbo 3.2.0 的filterChain 的核心类是DefaultFilterChainBuilder 。 Builder public class DefaultFilterChainBuilder implements FilterChainBuilder {的buildInvokerChain函数 对于consumer refer Overridepublic <T> Invoker<T> buildInvokerChain(final I…

Java自动化测试(web自动化测试框架 )

测试数据 测试地址 http://120.78.128.25:8765/ 投资人 13323234545 lemon123456 借款人 13323234444 lemonbest 后台地址 http://120.78.128.25:8765/Admin/Index/login.html lemon7 lemonbest Page Object PO简介 https://www.selenium.dev/documentation/en/g…

如何把在线K歌“玩起来”——专访撕歌音视频架构师程乐

编者按&#xff1a;在线K歌的业务已经发展了十年&#xff0c;程乐在音视频领域也闯荡了十年&#xff0c;甚至更久。为什么选择在线K歌领域&#xff1f;如何走过“漫长的季节”&#xff0c;迎来新的风景&#xff1f;如何在“在线K歌”这块难啃的骨头里分点肉&#xff1f;在这一连…

【存储】cache memory、primary memory and secondary memory

一、提要二、计算机的存储结构三、高速缓存&#xff1a;cache memory四、主存&#xff1a;Primary memory4.1 RAM4.11 SRAM 和 DRAM的概念4.12 SRAM 和 DRAM的应用场景 4.2 ROM4.21 PROM4.22 EPROM▶ EEPROM▶ UVEPROM 五、辅助存储器&#xff1a;secondary memory六、单片机的…

redis的4种模式,单机,哨兵、主从复制、集群

为了redis叫做redis服务器&#xff1f; 因为在运行时&#xff0c;在进行工作时是一个被注册一个进程(服务)&#xff0c;我们把他叫做一个redis服务器。(就是个应用程序而已。) 1.单机模式 我们安装redi,启动服务之后&#xff0c;默认就这个&#xff0c;只有一个redis服务器(…

编译原理笔记(哈工大编译原理)(持续更新)

文章目录 前言概论语言与文法基本概念字母表串字母表与串的联系 文法语言推导和规约句型与句子语言与字母表 文法的分类CFG的分析树 前言 说实话&#xff0c;我不是很想上这门课&#xff0c;确实没什么大用&#xff0c;虽然我觉得这门课学一学也挺好&#xff0c;但是我觉得弄8…

架构师必备项目管理方法-关键路径法

在《架构思维的六要素》中提到成本、规划、需求、维护、人员和质量是要考量的留个维度。咱们在日常工作中多少都会接触一些相关的管理方法&#xff0c;但是似乎是不知道也不影响干活&#xff0c;所以很多人也没有去深究。但实际上很可能知道了做事就更能抓住重点。 关键路径法是…

预测神经胶质瘤基因型的多模态学习

文章目录 Multi-modal learning for predicting the genotype of glioma摘要本文方法多模态数据生成Brain networks construction via self-supervised NNsMulti-modal learning for image, geometrics and brain networksBi-level multi-modal contrastive loss Population gr…

html实现好看的个人介绍,个人主页模板3(附源码)

文章目录 1.设计来源1.1 主界面1.2 关于我界面1.3 教育成就界面1.4 项目演示界面1.5 联系我界面 2.效果和源码2.1 动态效果2.2 源代码2.2 源代码目录 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/131263195 …

新手Maven入门(一)

Mavenue介绍和基本概念 一、什么是Maven1.1 Maven的组成1.2 安装和配置Maven1.2.1 下载1.2.2 安装 二、Maven 的基本概念2.1 标准的目录结构2.2 POM 大纲2.2.1 pom大纲展示 2.3 构件2.3.1 什么是maven的构建 2.4 POM 文件的用例2.5 GAV 坐标 三、依赖 一、什么是Maven Maven 是…

2023年前端面试汇总-计算机网络

1. HTTP协议 1.1. GET和POST的请求的区别 Post 和 Get 是 HTTP 请求的两种方法&#xff0c;其区别如下&#xff1a; 1. 应用场景 GET 请求是一个幂等的请求&#xff0c;一般 Get 请求用于对服务器资源不会产生影响的场景&#xff0c;比如说请求一个网页的资源。而 Post 不是…

如何在Ubuntu上安装MongoDB?

一、Ubuntu安装MongoDB MongoDB安装很简单&#xff0c;无需下载源文件&#xff0c;可以直接用apt-get命令进行安装。 打开终端&#xff0c;输入以下命令 sudo apt-get install mongodb这时装好以后应该会自动运行mongod程序&#xff0c;通过命令查看进程是否已经启动 pgrep …

Spring 实现AOP常见的两种方式(注解或者自定义注解)

第一种 导入AOP相关坐标(依赖冲突解决办法&#xff0c;将依赖中版本号删除&#xff0c;springboot会自动匹配合适的版本 ) <dependencies><!--spring核心依赖&#xff0c;会将spring-aop传递进来--><dependency><groupId>org.springframework</gr…

自动化测试必会之数据驱动测试

数据驱动测试 在实际的测试过程中&#xff0c;我们会发现好几组用例都是相同的操作步骤&#xff0c;只是测试数据的不同&#xff0c;而我们往往需要编写多次用例来进行测试&#xff0c;此时我们可以利用数据驱动测试来简化该种操作。 参数化&#xff1a; 输入数据的不同从而产…

C语言:输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。

题目&#xff1a; 描述 输入两个升序排列的序列&#xff0c;将两个序列合并为一个有序序列并输出。 输入描述&#xff1a; 输入包含三行&#xff0c; 第一行包含两个正整数n, m&#xff0c;用空格分隔。n表示第二行第一个升序序列中数字的个数&#xff0c;m表示第三…