Spring Boot 异常报告器解析
基于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 启动时将异常转化为可读消息的一种方法,系统自定义了很多异常报告器,通过接口也可以自定义异常报告器。
创建一个异常类:
public class MyException extends RuntimeException{
}
创建一个FailureAnalyzer:
public class MyFailureAnalyzer extends AbstractFailureAnalyzer<MyException> {@Overrideprotected 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 {//处理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,看下是如何处理的:
private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {int exitCode = getExitCodeFromException(context, exception);//exitCode非0if (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实现中获取exitCodeint exitCode = getExitCodeFromMappedException(context, exception);if (exitCode == 0) {//尝试从ExitCodeGenerator实现获取exitCodeexitCode = getExitCodeFromExitCodeGeneratorException(exception);}return exitCode;
}private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
//判断上下文是否是活动状态,上下文至少刷新过一次,不是就返回0if (context == null || !context.isActive()) {return 0;}//用于维护ExitCodeGenerator有序集合的组合器,ExitCodeGenerator 是一个接口,用于获取exitCodeExitCodeGenerators generators = new ExitCodeGenerators();//获取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();
}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())) {//获取当前线程的SpringBootExceptionHandlerreturn 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.analyzers在FailureAnalyzers创建的时候已经将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 定义的关闭钩子,用来优雅关机。
总结

相关文章:
Spring Boot 异常报告器解析
基于Spring Boot 3.1.0 系列文章 Spring Boot 源码阅读初始化环境搭建Spring Boot 框架整体启动流程详解Spring Boot 系统初始化器详解Spring Boot 监听器详解Spring Boot banner详解Spring Boot 属性配置解析Spring Boot 属性加载原理解析Spring Boot 异常报告器解析 创建自定…...
瑞亚太空活动公司RSA与英国国防与安全加速器达成量子项目合作
(图片来源:网络) 瑞亚太空活动公司(RSA)与英国国防与安全加速器(DASA)签署了合作协议,主要开发名为“无限交换”的可操纵量子真空的技术项目。这是RSA在英国签订的第一份合同&…...
Shapley值法介绍及实例计算
Shapley值法介绍及实例计算 为解决多个局中人在合作过程中因利益分配而产生矛盾的问题,属于合作博弈领域。应用 Shapley 值的一大优势是按照成员对联盟的边际贡献率将利益进行分配,即成员 i 所分得的利益等于该成员为他所参与联盟创造的边际利益的平均值…...
不用手动改 package.json 的版本号
“为什么package.json 里的版本还是原来的,有没有更新?”,这个时候我意识到,我们完全没有必要在每次发布的时候还特意去关注这个仓库的版本号,只要在发布打tag的时候同步一下即可 node.js 部分,我们得有一个…...
gitlab Can‘t update,dev has no tracked branch
代码仓库迁移到gitlab后本地更改仓库地址后 拉取代码报错: Can’t update,dev has no tracked branch: 解决办法: 在当前项目的目录下运行命令: git branch -u git dev --set-upstream-toorigin/dev第一个dev是本地分支名字&…...
sql批量操作
SQl: 1,在某一字段后批量增加内容:UPDATE 表名 SET 字段 CONCAT(字段,要增加的内容) 例:UPDATE b8_niuniu_permission SET game_ids CONCAT(game_ids,,3) (或者后面可以加where条件) 2,批量修改某一字段…...
数据库监控与调优【九】—— 索引数据结构
索引数据结构-B-Tree索引、Hash索引、空间索引、全文索引 二叉树查找 对于相同深度的节点,左侧的节点总是比右侧的节点小。在搜索时,如果要搜索的值key大于根节点(图中6),就会在右侧子树里查找;key小于根…...
哈工大计算机网络传输层详解之:流水线机制与滑动窗口协议
哈工大计算机网络传输层详解之:流水线机制与滑动窗口协议 哈工大计算机网络课程传输层协议详解之:可靠数据传输的基本原理哈工大计算机网络课程传输层协议详解之:TCP协议哈工大计算机网络课程传输层协议详解之:拥塞控制原理剖析 …...
Unity Mac最新打苹果包流程
作者介绍:铸梦xy。IT公司技术合伙人,IT高级讲师,资深Unity架构师,铸梦之路系列课程创始人。 IOS详细打包流程1.申请APPID2.申请开发证书3.创建描述文件 IOS详细打包流程 1.申请AppID 2.创建证书 3.申请配置文件(又名描…...
【MySQL数据库 | 第二十篇】explain执行计划
目录 前言: explain: 语法: 总结: 前言: 上一篇我们介绍了从时间角度分析MySQL语句执行效率的三大工具:SQL执行频率,慢日志查询,profile。但是这三个方法也只是在时间角度粗略的…...
学Python能做哪些副业?我一般不告诉别人!建议存好
前两天一个朋友找到我吐槽,说工资一发交完房租水电,啥也不剩,搞不懂朋友圈里那些天天吃喝玩乐的同龄人钱都是哪来的?确实如此,刚毕业的大学生工资起薪都很低,在高消费、高租金的城市,别说存钱&a…...
简化 Hello World:Java 新写法要来了
OpenJDK 的 JEP 445 提案正在努力简化 Java 的入门难度。 这个提案主要是引入 “灵活的 Main 方法和匿名 Main 类” ,希望 Java 的学习过程能更平滑,让学生和初学者能更好地接受 Java 。 提案的作者 Ron Pressler 解释:现在的 Java 语言非常…...
【服务器】springboot实现HTTP服务监听
文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…...
浅谈常见的加密算法及实现
浅谈常见的加密算法及实现 简介: 随着公司业务的发展,系统用户量日益增多,系统安全性问题一直在脑子里反复回旋,以前系统用户少影响面小,安全方面也一直没有进行思考和加固,现如今业务发展了,虽…...
FTP协议详解
简介 FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协…...
网络安全|渗透测试入门学习,从零基础入门到精通—渗透中的开发语言
目录 前面的话 开发语言 1、html 解析 2、JavaScript 用法 3、JAVA 特性 4、PHP 作用 PHP 能做什么? 5、C/C 使用 如何学习 前面的话 关于在渗透中需要学习的语言第一点个人认为就是可以打一下HTML,JS那些基础知识,磨刀不误砍柴…...
八大排序算法之归并排序(递归实现+非递归实现)
目录 一.归并排序的基本思想 归并排序算法思想(排升序为例) 二.两个有序子序列(同一个数组中)的归并(排升序) 两个有序序列归并操作代码: 三.归并排序的递归实现 递归归并排序的实现:(后序遍历递归) 递归函数抽象分析: 四.非递归归并排序的实现 1.非递归归并排序算法…...
基于SpringBoot+Html的前后端分离的学习平台
✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: 在知识大爆炸的现代,怎…...
MySQL实战解析底层---“order by“是怎么工作的
目录 前言 全字段排序 rowid排序 全字段排序 VS rowid排序 前言 在开发应用的时候,一定会经常碰到需要根据指定的字段排序来显示结果的需求以举例市民表为例,假设你要查询城市是“杭州”的所有人名字,并且按照姓名排序返回前1000个人的姓…...
Linux和Shell:开源力量与命令行之美
目录 一、概述二、Linux的简单介绍三、Shell的简单介绍四、Linux和Shell的应用领域五、Shell编程结语: 一、概述 Linux和Shell是开源世界中不可或缺的两个重要组成部分。Linux作为一种自由和开放的操作系统,以其稳定性、安全性和可定制性而备受推崇。而S…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
