Spring Boot - Application Events 的发布顺序_ApplicationFailedEvent

news2024/9/24 19:19:47

文章目录

  • Pre
  • 概述
  • Code
  • 源码分析

在这里插入图片描述


Pre

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent


概述

Spring Boot 的广播机制是基于观察者模式实现的,它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦,使得应用程序中的不同组件可以独立地改变和复用逻辑,而无需直接进行通信。

在 Spring Boot 中,事件发布和监听的机制是通过 ApplicationEventApplicationListener 以及事件发布者(ApplicationEventPublisher)来实现的。其中,ApplicationEvent 是所有自定义事件的基础,自定义事件需要继承自它。

ApplicationListener 是监听特定事件并做出响应的接口,开发者可以通过实现该接口来定义自己的监听器。事件发布者(通常由 Spring 的 ApplicationContext 担任)负责发布事件。


在Spring框架中,ApplicationFailedEvent 是一个特殊的事件,它代表了应用程序在启动过程中遇到的失败情况。这个事件是在Spring的应用程序生命周期中,当应用程序启动失败时触发的。

ApplicationFailedEvent 事件通常包含了有关失败原因的信息,例如异常类型、异常消息、发生错误的类和方法、以及失败发生的时间等。这个事件是Spring事件机制的一部分,它允许开发者在应用程序中实现事件驱动的设计。

在Spring框架中,事件机制是基于观察者模式的实现。事件发布者和事件监听器通过事件进行通信。在Spring中,事件发布者通常是通过 ApplicationEventPublisher 接口来进行操作的,而事件监听器则通过实现 ApplicationListener 接口来定义。

当Spring应用程序启动时,它会经历多个阶段。如果在某个阶段发生了错误,比如在初始化数据源时出现了异常,Spring会发布 ApplicationFailedEvent 事件。事件监听器可以监听这个事件,并对事件进行处理,比如记录日志、发送警报或者进行补偿操作等。

在Spring Boot应用程序中,ApplicationFailedEvent 事件也可以被用来处理启动时的异常情况。Spring Boot提供了一种更简化的方式来监听这个事件,即使用 @EventListener 注解。这种方式可以让开发者更容易地编写事件监听器,而不需要实现复杂的接口。

例如,以下是一个简单的 @EventListener 注解的使用示例,用于监听 ApplicationFailedEvent 事件:

@Component
public class ApplicationFailedListener {
    @EventListener
    public void onApplicationFailedEvent(ApplicationFailedEvent event) {
        Throwable throwable = event.getException();
        // 对异常进行处理,比如记录日志
        System.err.println("Application failed to start: " + throwable.getMessage());
    }
}

当应用程序启动失败时,这个监听器会被触发,并可以执行相应的错误处理逻辑。这样,开发者可以更好地管理应用程序的启动过程,并在遇到失败时进行适当的响应。


Code

package com.artisan.event;

import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationListener;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class ApplicationFailedListener implements ApplicationListener<ApplicationFailedEvent> {


    @Override
    public void onApplicationEvent(ApplicationFailedEvent event) {
        System.out.println("--------------------> Handling ApplicationFailedEvent here!");

        Throwable throwable = event.getException();
        // 对异常进行处理,比如记录日志
        System.err.println("Application failed to start: " + throwable.getMessage());
    }
}
    
    

如何使用呢?

方式一:

@SpringBootApplication
public class LifeCycleApplication {

    /**
     * 除了手工add , 在 META-INF下面 的 spring.factories 里增加
     * org.springframework.context.ApplicationListener=自定义的listener 也可以
     *
     * @param args
     */
    public static void main(String[] args) {
      
       SpringApplication springApplication = new SpringApplication(LifeCycleApplication.class);

       springApplication.addListeners(new ApplicationFailedListener());

       springApplication.run(args);
    }


}

方式二: 通过spring.factories 配置

在这里插入图片描述

org.springframework.context.ApplicationListener=\
com.artisan.event.ApplicationFailedListener

运行日志

在这里插入图片描述


源码分析

首先main方法启动入口

SpringApplication.run(LifeCycleApplication.class, args);

跟进去

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

继续

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

这里首先关注 new SpringApplication(primarySources)

new SpringApplication(primarySources)

	/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

聚焦 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));


run

继续run

// 开始启动Spring应用程序
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch(); // 创建一个计时器
    stopWatch.start(); // 开始计时
    
    DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // 创建引导上下文
    ConfigurableApplicationContext context = null; // Spring应用上下文,初始化为null
    
    configureHeadlessProperty(); // 配置无头属性(如:是否在浏览器中运行)

    SpringApplicationRunListeners listeners = getRunListeners(args); // 获取运行监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass); // 通知监听器启动过程开始
    
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 创建应用参数
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // 预备环境
        configureIgnoreBeanInfo(environment); // 配置忽略BeanInfo
        
        Banner printedBanner = printBanner(environment); // 打印Banner
        context = createApplicationContext(); // 创建应用上下文
        context.setApplicationStartup(this.applicationStartup); // 设置应用启动状态
        
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 准备上下文
        refreshContext(context); // 刷新上下文,执行Bean的生命周期
        afterRefresh(context, applicationArguments); // 刷新后的操作
        
        stopWatch.stop(); // 停止计时
        if (this.logStartupInfo) { // 如果需要记录启动信息
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); // 记录启动信息
        }
        listeners.started(context); // 通知监听器启动完成
        
        callRunners(context, applicationArguments); // 调用Runner
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners); // 处理运行失败
        throw new IllegalStateException(ex); // 抛出异常
    }

    try {
        listeners.running(context); // 通知监听器运行中
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null); // 处理运行失败
        throw new IllegalStateException(ex); // 抛出异常
    }
    return context; // 返回应用上下文
}

我们重点看

  handleRunFailure(context, ex, listeners); // 处理运行失败

继续

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
			SpringApplicationRunListeners listeners) {
		try {
			try {
				handleExitCode(context, exception);
				if (listeners != null) {
					listeners.failed(context, exception);
				}
			}
			finally {
				reportFailure(getExceptionReporters(context), exception);
				if (context != null) {
					context.close();
				}
			}
		}
		catch (Exception ex) {
			logger.warn("Unable to close ApplicationContext", ex);
		}
		ReflectionUtils.rethrowRuntimeException(exception);
	}

继续 listeners.failed(context, exception);

	void failed(ConfigurableApplicationContext context, Throwable exception) {
		doWithListeners("spring.boot.application.failed",
				(listener) -> callFailedListener(listener, context, exception), (step) -> {
					step.tag("exception", exception.getClass().toString());
					step.tag("message", exception.getMessage());
				});
	}

继续 callFailedListener;

private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
			Throwable exception) {
		try {
			listener.failed(context, exception);
		}
		catch (Throwable ex) {
			if (exception == null) {
				ReflectionUtils.rethrowRuntimeException(ex);
			}
			if (this.log.isDebugEnabled()) {
				this.log.error("Error handling failed", ex);
			}
			else {
				String message = ex.getMessage();
				message = (message != null) ? message : "no error message";
				this.log.warn("Error handling failed (" + message + ")");
			}
		}
	}
@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

context.publishEvent(event);

@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

继续this.initialMulticaster.multicastEvent(event);

	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

继续

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    // 如果eventType不为null,则直接使用它;否则,使用resolveDefaultEventType方法来解析事件的默认类型。
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    
    // 获取一个线程池执行器,它用于异步执行监听器调用。
    Executor executor = getTaskExecutor();
    
    // 获取所有对应该事件类型的监听器。
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        // 如果执行器不为null,则使用它来异步执行监听器调用;
        // 否则,直接同步调用监听器。
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

继续

/**
 * 调用一个事件监听器的方法。
 *
 * @param listener 要调用的监听器
 * @param event 要处理的事件
 */
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        // 直接调用监听器的onApplicationEvent方法
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        // 如果消息为null或者消息匹配事件类的预期类型,则忽略异常并记录debug日志
        if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
            Log logger = LogFactory.getLog(getClass());
            if (logger.isTraceEnabled()) {
                logger.trace("Non-matching event type for listener: " + listener, ex);
            }
        }
        // 否则,抛出异常
        else {
            throw ex;
        }
    }
}

继续 就会调到我们自己的业务逻辑了

 @Override
    public void onApplicationEvent(ApplicationFailedEvent event) {
        System.out.println("--------------------> Handling ApplicationFailedEvent here!");

        Throwable throwable = event.getException();
        // 对异常进行处理,比如记录日志
        System.err.println("Application failed to start: " + throwable.getMessage());
    }

在这里插入图片描述

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

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

相关文章

Redis实现分布式会话

Redis实现分布式会话 1 什么是分布式会话 1 这是我么之前学过的注册登录模式 2 如果非常多的人访问&#xff0c;因为单台服务器的访问承受能力是有限的&#xff0c;那么我们就想用多态服务器来承担压力 3 一般通过负载均衡的方式来实现&#xff0c;来分担服务器的压力。 4 负…

了解集群,以及集群是什么?

每个集群即一个独立运行的文档数据库&#xff0c;分片集群架构由路由&#xff08;mongos&#xff09;、配置&#xff08;config&#xff09;和分片&#xff08;shard&#xff09;组成。 数据读写请求经mongos分发&#xff0c;通过查询config信息&#xff0c;并行分配到相应sha…

基于完整熵编码系数组的JPEG图像加密方案

论文题目&#xff1a;JPEG image encryption with grouping coefficients based on entropy coding 期刊&#xff1a;Journal of Visual Communication and Image Representation 分区&#xff1a;中科苑三区&#xff0c;老牌图像处理期刊 文章目录 摘要概要整体架构流程实验结…

在MinIO中添加Pools(池)并扩展容量

服务器池可帮助您快速轻松地扩展现有 MinIO 集群的容量。这篇博文重点介绍如何增加一个集群的容量&#xff0c;这与添加另一个集群并在多个集群之间复制相同数据不同。将服务器池添加到现有群集时&#xff0c;可以增加该群集的整体可用容量。如果设置了复制&#xff0c;则需要平…

2024年广东省安全员C证第四批(专职安全生产管理人员)证模拟考试题库及广东省安全员C证第四批(专职安全生产管理人员)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;证模拟考试题库及广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;理论考试试题是由安全生产模拟考试一点通提供&#…

分布式数据库原理及技术实验及个人思考

Hive的数据库及表的存储结构体系讨论 1.显示hive所在数据库的位置 方法一&#xff1a;一次性临时存储 >hive set hive.cli.print.current.dbtrue 方法二&#xff1a;永久存储 在conf文件夹下修改hive-site.xml配置文件&#xff0c;添加 <property> <name>…

报错解决:No module named ‘pytorch_lightning‘ 安装pytorch_lightning

报错记录 执行如下代码&#xff1a; import pytorch_lightning报错&#xff1a; No module named ‘pytorch_lightning’ 解决方式 安装pytorch_lightning包即可。 一般情况下&#xff0c;缺失的包通过pip安装&#xff0c;即&#xff1a; pip install pytorch_lightning然…

构建 Maven 项目时可能遇到的问题

文章目录 构建 Maven 项目时可能遇到的问题1. Maven 自动下载依赖后&#xff0c;在本地仓库中找不到2. 运行时报错如下&#xff1a;Error: java 不支持发行版本 53. 创建 Maven 项目后 pom.xml 文件为空4. 在 Settings 中 Update 了阿里云远程仓库&#xff0c;导致整个项目不能…

【Web】NSSCTF Round#16 Basic个人wp(全)

出题友好&#xff0c;适合手生复健。 目录 ①RCE但是没有完全RCE ②了解过PHP特性吗 ①RCE但是没有完全RCE 上来就是一段md5八股 (string)就是不让用数组了&#xff0c;然后强比较需要md5碰撞 ?md5_1%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc…

【Python机器学习】分类器的不确定估计——决策函数

scikit-learn接口的分类器能够给出预测的不确定度估计&#xff0c;一般来说&#xff0c;分类器会预测一个测试点属于哪个类别&#xff0c;还包括它对这个预测的置信程度。 scikit-learn中有两个函数可以用于获取分类器的不确定度估计&#xff1a;decidion_function和predict_pr…

Portalgraph VR空间投影仪:可以将VR空间投射到任意平面上的新型VR投影技术

通过一项创新的科技突破&#xff0c;Portalgraph VR空间投影仪成功地在现实与虚拟空间之间搭建起了一座神奇的“时空传送门”。这投影一技术不仅打破了传统虚拟现实设备的局限&#xff0c;更让人们无需佩戴任何头戴显示器&#xff0c;仅凭裸眼就能在任何平面上看到虚拟现实空间…

平衡小车——PID控制理论

开环控制 开环控制,全称开环控制系统(Open Loop Control System),又称为无反馈系统。即系统的输入可以影响输出,但是 输入不受输出影响 的系统。输入到输出的信号是单向传递的。 以下为生活中的例子: 控制系统 输入量 控制器 受控对象 输出量 风扇调速

基于代理IP的多线程爬虫实现

目录 前言 1. 爬虫的基本原理 2. 多线程爬虫的优势 3. 代理IP的应用 4. 基于代理IP的多线程爬虫实现 步骤1&#xff1a;导入必要的模块 步骤2&#xff1a;定义爬虫函数 步骤3&#xff1a;创建线程并启动爬虫 总结 前言 本文将介绍如何使用Python编写一个基于代理IP的多…

二十几种未授权访问漏洞合集

未授权访问漏洞是一个在企业内部非常常见的问题&#xff0c;这种问题通常都是由于安全配置不当、认证页面存在缺陷&#xff0c;或者压根就没有认证导致的。当某企业对外的服务端口、功能无限制开放&#xff0c;并且对用户的访问没有做任何限制的时候&#xff0c;可能会泄露出某…

C++重新认知:头文件的预处理

一、为什么头文件需要预处理 预处理的功能是对一个资源进行替换。 预处理的几种形式 常见的预处理指令&#xff1a; #define 宏定义 #undef 取消宏 #include 文本包含 #ifdef 如果宏被定义就进行编译 #ifndef 如果宏未被定义就进行编译 #endif 结束编译块的控制 #if 表达式非…

m1 + swoole(hyperf) + yasd + phpstorm 安装和debug

参考文档 Mac M1安装报错 checking for boost... configure: error: lib boost not found. Try: install boost library Issue #89 swoole/yasd GitHub 1.安装boost库 brew install boostbrew link boost 2.下载yasd git clone https://github.com/swoole/yasd.git 3.编…

【书生·浦语】大模型实战营——第四课笔记

教程链接&#xff1a;https://github.com/InternLM/tutorial/blob/main/xtuner/README.md 视频链接&#xff1a;https://www.bilibili.com/video/BV1yK4y1B75J/?vd_source5d94ee72ede352cb2dfc19e4694f7622 本次视频的内容分为以下四部分&#xff1a; 目录 微调简介 微调会使…

YOLOv5涨点改进:多层次特征融合(SDI),小目标涨点明显,| UNet v2,比UNet显存占用更少、参数更少

💡💡💡本文全网独家改进:多层次特征融合(SDI),能够显著提升不同尺度和小目标的识别率 💡💡💡在YOLOv5中如何使用 1)iAFF加入Neck替代Concat; 💡💡💡Yolov5/Yolov7魔术师,独家首发创新(原创),适用于Yolov5、Yolov7、Yolov8等各个Yolo系列,专栏文…

灰色关联度分析详细Stata代码和说明(代码+案例数据+说明)

灰色关联度分析详细Stata代码和说明&#xff08;代码案例数据说明&#xff09; 因素分析的基本方法过去采用的主要是统计的方法&#xff0c;如回归分析&#xff0c;回归分析虽然是一种较通用的方法&#xff0c;但大都只用于少因素的、线性的。 对于多因素的&#xff0c;非线性…

漏洞分析|Cacti命令执行漏洞 (CVE-2022-46169)

1.漏洞描述 Cacti是一套基于PHP&#xff0c;MySQL&#xff0c;SNMP及RRDTool开发的网络流量监测图形分析工具&#xff0c;可为用户提供强大且可扩展的操作监控和故障管理框架。 该漏洞存在于remote_agent.php文件中&#xff0c;未经身份验证的恶意攻击者可以通过设置HTTP_变量…