当前位置: 首页 > news >正文

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

创建一个异常类:

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.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 定义的关闭钩子,用来优雅关机。

总结

在这里插入图片描述

相关文章:

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与英国国防与安全加速器达成量子项目合作

​ &#xff08;图片来源&#xff1a;网络&#xff09; 瑞亚太空活动公司&#xff08;RSA&#xff09;与英国国防与安全加速器&#xff08;DASA&#xff09;签署了合作协议&#xff0c;主要开发名为“无限交换”的可操纵量子真空的技术项目。这是RSA在英国签订的第一份合同&…...

Shapley值法介绍及实例计算

Shapley值法介绍及实例计算 为解决多个局中人在合作过程中因利益分配而产生矛盾的问题&#xff0c;属于合作博弈领域。应用 Shapley 值的一大优势是按照成员对联盟的边际贡献率将利益进行分配&#xff0c;即成员 i 所分得的利益等于该成员为他所参与联盟创造的边际利益的平均值…...

不用手动改 package.json 的版本号

“为什么package.json 里的版本还是原来的&#xff0c;有没有更新&#xff1f;”&#xff0c;这个时候我意识到&#xff0c;我们完全没有必要在每次发布的时候还特意去关注这个仓库的版本号&#xff0c;只要在发布打tag的时候同步一下即可 node.js 部分&#xff0c;我们得有一个…...

gitlab Can‘t update,dev has no tracked branch

代码仓库迁移到gitlab后本地更改仓库地址后 拉取代码报错&#xff1a; Can’t update,dev has no tracked branch&#xff1a; 解决办法&#xff1a; 在当前项目的目录下运行命令&#xff1a; git branch -u git dev --set-upstream-toorigin/dev第一个dev是本地分支名字&…...

sql批量操作

SQl: 1&#xff0c;在某一字段后批量增加内容&#xff1a;UPDATE 表名 SET 字段 CONCAT(字段,要增加的内容) 例&#xff1a;UPDATE b8_niuniu_permission SET game_ids CONCAT(game_ids,,3) &#xff08;或者后面可以加where条件&#xff09; 2&#xff0c;批量修改某一字段…...

数据库监控与调优【九】—— 索引数据结构

索引数据结构-B-Tree索引、Hash索引、空间索引、全文索引 二叉树查找 对于相同深度的节点&#xff0c;左侧的节点总是比右侧的节点小。在搜索时&#xff0c;如果要搜索的值key大于根节点&#xff08;图中6&#xff09;&#xff0c;就会在右侧子树里查找&#xff1b;key小于根…...

哈工大计算机网络传输层详解之:流水线机制与滑动窗口协议

哈工大计算机网络传输层详解之&#xff1a;流水线机制与滑动窗口协议 哈工大计算机网络课程传输层协议详解之&#xff1a;可靠数据传输的基本原理哈工大计算机网络课程传输层协议详解之&#xff1a;TCP协议哈工大计算机网络课程传输层协议详解之&#xff1a;拥塞控制原理剖析 …...

Unity Mac最新打苹果包流程

作者介绍&#xff1a;铸梦xy。IT公司技术合伙人&#xff0c;IT高级讲师&#xff0c;资深Unity架构师&#xff0c;铸梦之路系列课程创始人。 IOS详细打包流程1.申请APPID2.申请开发证书3.创建描述文件 IOS详细打包流程 1.申请AppID 2.创建证书 3.申请配置文件&#xff08;又名描…...

【MySQL数据库 | 第二十篇】explain执行计划

目录 前言&#xff1a; explain&#xff1a; 语法&#xff1a; 总结&#xff1a; 前言&#xff1a; 上一篇我们介绍了从时间角度分析MySQL语句执行效率的三大工具&#xff1a;SQL执行频率&#xff0c;慢日志查询&#xff0c;profile。但是这三个方法也只是在时间角度粗略的…...

学Python能做哪些副业?我一般不告诉别人!建议存好

前两天一个朋友找到我吐槽&#xff0c;说工资一发交完房租水电&#xff0c;啥也不剩&#xff0c;搞不懂朋友圈里那些天天吃喝玩乐的同龄人钱都是哪来的&#xff1f;确实如此&#xff0c;刚毕业的大学生工资起薪都很低&#xff0c;在高消费、高租金的城市&#xff0c;别说存钱&a…...

简化 Hello World:Java 新写法要来了

OpenJDK 的 JEP 445 提案正在努力简化 Java 的入门难度。 这个提案主要是引入 “灵活的 Main 方法和匿名 Main 类” &#xff0c;希望 Java 的学习过程能更平滑&#xff0c;让学生和初学者能更好地接受 Java 。 提案的作者 Ron Pressler 解释&#xff1a;现在的 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 测试使用固定公网地址…...

浅谈常见的加密算法及实现

浅谈常见的加密算法及实现 简介&#xff1a; 随着公司业务的发展&#xff0c;系统用户量日益增多&#xff0c;系统安全性问题一直在脑子里反复回旋&#xff0c;以前系统用户少影响面小&#xff0c;安全方面也一直没有进行思考和加固&#xff0c;现如今业务发展了&#xff0c;虽…...

FTP协议详解

简介 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09; 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分&#xff0c;其一为FTP服务器&#xff0c;其二为FTP客户端。其中FTP服务器用来存储文件&#xff0c;用户可以使用FTP客户端通过FTP协…...

网络安全|渗透测试入门学习,从零基础入门到精通—渗透中的开发语言

目录 前面的话 开发语言 1、html 解析 2、JavaScript 用法 3、JAVA 特性 4、PHP 作用 PHP 能做什么&#xff1f; 5、C/C 使用 如何学习 前面的话 关于在渗透中需要学习的语言第一点个人认为就是可以打一下HTML&#xff0c;JS那些基础知识&#xff0c;磨刀不误砍柴…...

八大排序算法之归并排序(递归实现+非递归实现)

目录 一.归并排序的基本思想 归并排序算法思想(排升序为例) 二.两个有序子序列(同一个数组中)的归并(排升序) 两个有序序列归并操作代码: 三.归并排序的递归实现 递归归并排序的实现:(后序遍历递归) 递归函数抽象分析: 四.非递归归并排序的实现 1.非递归归并排序算法…...

基于SpringBoot+Html的前后端分离的学习平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 在知识大爆炸的现代,怎…...

MySQL实战解析底层---“order by“是怎么工作的

目录 前言 全字段排序 rowid排序 全字段排序 VS rowid排序 前言 在开发应用的时候&#xff0c;一定会经常碰到需要根据指定的字段排序来显示结果的需求以举例市民表为例&#xff0c;假设你要查询城市是“杭州”的所有人名字&#xff0c;并且按照姓名排序返回前1000个人的姓…...

Linux和Shell:开源力量与命令行之美

目录 一、概述二、Linux的简单介绍三、Shell的简单介绍四、Linux和Shell的应用领域五、Shell编程结语&#xff1a; 一、概述 Linux和Shell是开源世界中不可或缺的两个重要组成部分。Linux作为一种自由和开放的操作系统&#xff0c;以其稳定性、安全性和可定制性而备受推崇。而S…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...