Spring Boot - Application Events 的发布顺序_ApplicationStartingEvent

文章目录

  • 概述
  • Code
  • 源码分析

在这里插入图片描述

概述

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

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

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

Spring Boot中的ApplicationStartingEvent是在应用程序启动的最早阶段触发的事件。它在应用程序上下文完全初始化之前触发,此时应用程序刚刚启动,各种初始化任务(如加载配置文件和设置环境)尚不可用。

通过监听ApplicationStartingEvent,我们可以执行一些预初始化任务,例如更新系统属性、读取外部配置、初始化日志框架等。


Code

在这里插入图片描述

package com.artisan.event;import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationListener;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class ApplicationStartingListener implements ApplicationListener<ApplicationStartingEvent> {/*** ApplicationStartingEvent 在应用程序启动过程的一开始,就在应用程序上下文完全初始化之前触发。* 此时,应用程序刚刚启动,加载配置文件、设置环境等各种初始化任务尚不可用。* <p>* <p>* 通过监听 ,我们可以执行预初始化任务 ApplicationStartingEvent ,例如更新系统属性、读取外部配置、初始化日志记录框架等* <p>* <p>* 为了处理 ApplicationStartingEvent ,我们可以通过实现 ApplicationListener 作为 ApplicationStartingEvent 泛型类型的接口来创建一个自定义事件侦听器。* 此侦听器可以在主应用程序类中手动注册** @param event the event to respond to*/@Overridepublic void onApplicationEvent(ApplicationStartingEvent event) {System.out.println("--------------------> Handling ApplicationStartingEvent here!");}
}

如何使用呢?

方式一:

@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);// 当应用程序运行时,将调用 的方法 ApplicationStartingListener#onApplicationEvent()// 允许我们在应用程序上下文完全建立之前执行任何必要的任务。springApplication.addListeners(new ApplicationStartingListener());springApplication.run(args);}}

方式二: 通过spring.factories 配置

在这里插入图片描述

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

当应用程序运行时,ApplicationStartingListener#onApplicationEvent()方法将被调用,从而允许我们在应用程序上下文完全建立之前执行任何必要的任务

运行日志

在这里插入图片描述


源码分析

首先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));

通过spring.factories 配置的方式会扫描到 然后 setListeners

在这里插入图片描述


run

继续run

// 开始启动Spring应用程序
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch(); // 创建一个计时器stopWatch.start(); // 开始计时DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // 创建引导上下文ConfigurableApplicationContext context = null; // Spring应用上下文,初始化为nullconfigureHeadlessProperty(); // 配置无头属性(如:是否在浏览器中运行)SpringApplicationRunListeners listeners = getRunListeners(args); // 获取运行监听器listeners.starting(bootstrapContext, this.mainApplicationClass); // 通知监听器启动过程开始try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 创建应用参数ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // 预备环境configureIgnoreBeanInfo(environment); // 配置忽略BeanInfoBanner printedBanner = printBanner(environment); // 打印Bannercontext = 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; // 返回应用上下文
}

我们重点看

    SpringApplicationRunListeners listeners = getRunListeners(args); // 获取运行监听器listeners.starting(bootstrapContext, this.mainApplicationClass); // 通知监听器启动过程开始

在这里插入图片描述

继续查看 starting

// 用于执行与特定步骤相关的监听器和动作。
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,Consumer<StartupStep> stepAction) {// 获取一个StartupStep实例,这个实例对应于指定的stepName。StartupStep step = this.applicationStartup.start(stepName);// 遍历所有的监听器,并执行动作(这些动作通常是启动监听器)。this.listeners.forEach(listenerAction);// 如果存在步骤动作(stepAction),则执行它。步骤动作可以用来添加额外的信息或执行额外的逻辑。if (stepAction != null) {stepAction.accept(step);}// 完成当前步骤。step.end();
}
// 这个方法是在Spring Boot应用启动的时候被调用的。它接收一个可配置的启动上下文和一个主应用类(main class)。
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {// 使用doWithListeners方法来执行一个动作,这个动作是在spring.boot.application.starting这个阶段执行的。// 它会调用所有的SpringApplicationRunListener监听器来完成启动过程。doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),(step) -> {// 如果主应用类不为null,给当前步骤添加一个标签,标签的值是主应用类的名字。if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}});
}

在这里插入图片描述

在这里插入图片描述

发布事件

	@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));}

继续

	@Overridepublic 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);}}
}

继续

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {listener.onApplicationEvent(event);}catch (ClassCastException ex) {......}}

在这里插入图片描述

就到了我们自定义实现的代码逻辑中了。

  @Overridepublic void onApplicationEvent(ApplicationStartingEvent event) {System.out.println("--------------------> Handling ApplicationStartingEvent here!");}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java SE入门及基础(8)

关系运算符和逻辑运算符 1. 关系运算符 关系运算符包含 > < > < ! boolean result 2 > 3 ; boolean result1 10 10 ; 关系运算符比较的结果是一个布尔值 2. 逻辑运算符 逻辑运算符包含&#xff1a; 逻辑与 &&&#xff…

中间人攻击如何进行防护

中间人攻击&#xff08;Man-in-the-Middle Attack&#xff0c;简称 MITM 攻击&#xff09;是一种常见的网络攻击方式&#xff0c;攻击者通过截获两个通信实体之间的通信数据&#xff0c;并在此基础上进行篡改、窃取或伪造等恶意行为。这种攻击方式因其攻击手段的隐蔽性和难以防…

城市排水管网监测系统主要功能与特点

城市排水管网监测系统解决方案&#xff0c;通过在管网上安装数据监测设备&#xff0c;工作人员在调度中心即可远程监测整个管网的压力、流量、水质等参数&#xff0c;实现泵站远程监控及阀门远程控制&#xff0c;保障管网压力平衡和流量稳定&#xff0c;及时发现和预测爆管事故…

java SSM物资采购管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM物资采购管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

鸿蒙原生应用再添新丁!天眼查 入局鸿蒙

鸿蒙原生应用再添新丁&#xff01;天眼查 入局鸿蒙 来自 HarmonyOS 微博1月12日消息&#xff0c;#天眼查启动鸿蒙原生应用开发#作为累计用户数超6亿的头部商业信息查询平台&#xff0c;天眼查可以为商家企业&#xff0c;职场人士以及普通消费者等用户便捷和安全地提供查询海量…

各版本 操作系统 对 .NET Framework 与 .NET Core 支持

有两种类型的受支持版本&#xff1a;长期支持 (LTS) 版本和标准期限支持 (STS) 版本。 所有版本的质量都是一样的。 唯一的区别是支持的时间长短。 LTS 版本可获得为期三年的免费支持和补丁。 STS 版本可获得 18 个月的免费支持和修补程序。 有关详细信息&#xff0c;请参阅 .N…

学完C/C++,再学Python是一种什么体验?

你好&#xff0c;我是安然无虞。 文章目录 变量及类型变量类型动态类型特性 注释输入输出通过控制台输出通过控制台输入 运算符算术运算符关系运算符逻辑运算符赋值运算符 条件循环语句条件语句语法格式代码案例缩进和代码块空语句pass 循环语句while循环语法格式代码案例 for…

LeetCode[105] 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,15,7] …

1.1 计算机网络在信息时代的作用

1.1 计算机网络在信息时代的作用 网络&#xff08;Network&#xff09;由若干结点&#xff08;Node&#xff09;和连接这些结点的链路&#xff08;Link&#xff09;所组成。网络中的结点可以是计算机、集线器、交换机或者路由器等。 图1-1 多个网络还可以通过路由器互连起来&a…

【QT】标准对话框

目录 1 概述 2 QFileDialog对话框 1.选择打开一个文件 2.选择打开多个文件 3&#xff0e;选择已有目录 4&#xff0e;选择保存文件名 3 QColorDialog对话框 4 QFontDialog对话框 5 QInputDialog标准输入对话框 1.输入文字 2&#xff0e;输入整数 3&#xff0e;输入…

二叉树-遍历-单独精讲

文章目录 遍历中序遍历/节点的中序前序遍历-节点的前序后序遍历-节点的后序三序综合13-Apush前/前序前13-Bpush前/中序前13-Cpush前/后序前 两序重叠示例一13前序前13中序前 示例二13前序前13后序前 示例三13中序前13后序前 遍历 遍历 即:遍历每个元素。 for遍历只会遍历每个…

商业定位,1元平价商业咨询:豪威尔咨询!平价咨询。

在做生意之前&#xff0c;就需要对企业整体进行一完整的商业定位&#xff0c;才能让商业定位带动企业进行飞速发展。 所以&#xff0c;包含商业定位的有效工作内容就显得极为重要&#xff0c;今天&#xff0c;小编特地为大家整理出了商业定位所需要的筹备的工作&#xff0c;如下…