Spring Boot - Application Events 的发布顺序_ApplicationReadyEvent

文章目录

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

在这里插入图片描述


Pre

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent


概述

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

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

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


ApplicationReadyEvent 是 Spring Boot 中的一个应用事件,表示应用程序已准备好处理请求。这个事件是在应用程序启动后尽可能晚地发布的,以指示应用程序已准备好服务请求。你可以使用这个事件来执行一些初始化后的操作,比如启动后台任务、调度作业、建立与外部系统的连接等。

你可以创建一个自定义事件监听器来处理 ApplicationReadyEvent,并在监听器中执行你需要的操作。下面是一个示例代码:

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;public class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// 执行应用程序准备好后的操作System.out.println("应用程序已准备好处理请求!");}
}

然后,在你的主应用程序类中注册这个监听器:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class YourApplication {public static void main(String[] args) {SpringApplication app = new SpringApplication(YourApplication.class);app.addListeners(new ApplicationReadyListener());app.run(args);}
}

这样,当应用程序启动后,ApplicationReadyListener 中的 onApplicationEvent 方法将被调用,你可以在这个方法中执行你需要的操作。


在处理 ApplicationReadyEvent 时,你可以使用 @Order 注解来指定操作的顺序和优先级。通过为不同的操作指定不同的顺序值,你可以确保它们按照你期望的顺序执行。

以下是一个示例代码,演示了如何使用 @Order 注解来指定操作的执行顺序:

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;@Order(1)
public class FirstApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// 第一个操作的逻辑}
}@Order(2)
public class SecondApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// 第二个操作的逻辑}
}

在这个示例中,FirstApplicationReadyListenerSecondApplicationReadyListener 分别使用了 @Order 注解指定了它们的执行顺序。@Order 注解的值越小,优先级越高,因此 FirstApplicationReadyListener 的优先级高于 SecondApplicationReadyListener,它们将按照指定的顺序执行。

通过这种方式,你可以确保 ApplicationReadyEvent 中的操作按照你期望的顺序执行,并且可以保证它们的可靠性。


Code

package com.artisan.event;import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class ApplicationStartedListener implements ApplicationListener<ApplicationStartedEvent> {/*** ApplicationStartedEvent 在应用程序启动并执行 main 方法时触发。* 它发生在创建和准备应用程序上下文之后,但在实际执行应用程序 run() 方法之前。* <p>* <p>* 此事件提供了在应用程序启动时执行其他任务或执行自定义逻辑的机会,包括初始化其他组件、设置日志记录、配置动态属性等。* <p>* <p>* 为了处理 ApplicationStartedEvent ,我们可以通过实现 ApplicationStartedEvent 作为泛型类型的 ApplicationListener 接口来创建一个自定义事件侦听器。* 此侦听器可以在主应用程序类中手动注册** @param event the event to respond to*/@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {System.out.println("--------------------> Handling ApplicationStartedEvent 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);// 当应用程序完全初始化并准备就绪时,将调用 的方法 ApplicationReadyListener , onApplicationEvent() 从而根据需要执行自定义逻辑springApplication.addListeners(new ApplicationReadyListener());springApplication.run(args);}}

方式二: 通过spring.factories 配置

在这里插入图片描述

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

运行日志

在这里插入图片描述


源码分析

首先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应用上下文,初始化为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; // 返回应用上下文
}

我们重点看

 listeners.running(context); // 通知监听器运行中

继续

void running(ConfigurableApplicationContext context) {doWithListeners("spring.boot.application.running", (listener) -> listener.running(context));}

继续 listener.running(context)

 @Overridepublic void running(ConfigurableApplicationContext context) {context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);}

继续 context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));

/*** 发布一个事件,这个事件可以被订阅者处理。** @param event 事件对象,不能为null* @param eventType 事件类型,用于类型匹配和事件处理器的选择*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {// 断言事件对象不能为nullAssert.notNull(event, "Event must not be null");// 如果事件本身就是ApplicationEvent的实例,直接使用ApplicationEvent applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;}// 否则,使用PayloadApplicationEvent进行包装else {applicationEvent = new PayloadApplicationEvent<>(this, event);// 如果事件类型为null,从PayloadApplicationEvent中获取if (eventType == null) {eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();}}// 如果有提前注册的事件,直接添加到列表中if (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}// 否则,使用ApplicationEventMulticaster进行广播else {getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// 如果有父上下文,也发布事件if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);} else {this.parent.publishEvent(event);}}
}

请关注: getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

继续

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

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

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

在这里插入图片描述

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

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

相关文章

Hades-C2:一款功能强大的纯Python命令控制服务器

关于Hades-C2 Hades-C2是一款功能强大的命令控制服务器&#xff0c;该工具基于纯Python开发&#xff0c;可以帮助广大研究人员快速实现命令控制基础设施的搭建。 当前版本的Hades-C2可以用作安全分析研究或CTF比赛&#xff0c;但功能并不完善&#xff0c;目前该项目仍在积极开…

Java之Stream类

1.介绍 &#xff08;1&#xff09;Stream流 配合Lambda表达式&#xff0c;简化集合和数组的操作 &#xff08;2&#xff09;Stream流思想 流水线思想 2.获取Stream流对象 &#xff08;1&#xff09;集合获取流Stream流对象 使用Collection接口中的默认方法Stream<T> s…

Python学习之路-综合练习:学生管理系统

Python学习之路-综合练习:学生管理系统 目前已经学习了变量、流程控制、函数、模块&#xff0c;可以利用已学习的知识开发一个学生管理系统 项目需求 系统有首页介绍页面与功能菜单系统功能由查询、显示、修改与删除功能可以使用数字选择不同的功能学生信息需要记录&#xf…

Trans论文复现:基于数据驱动的新能源充电站两阶段规划方法程序代码!

适用平台&#xff1a;MatlabYalmipCplex/Gurobi&#xff1b; 文章提出了一种电动汽车充电站的两阶段规划方法&#xff0c;第一阶段通过蒙特卡洛法模拟充电车辆需求和电池充放电数据来确定充电站位置&#xff1b;第二阶段通过数据驱动的分布鲁棒优化方法优化充电站的新能源和电池…

【惠友骨科小课堂】拇外翻常见的几个误区,来看看你中了几个?

拇外翻作为常见的足部畸形&#xff0c;在日常生活中困扰着许多人。歪脚趾不仅外观不好看&#xff0c;还会出现疼痛、影响行走运动。但大多数人对于拇外翻的认识都不足常常落入认知误区&#xff0c;快来看看你中了几个&#xff1f; 误区一Q 我都没穿过高跟鞋&#xff0c;怎么也…

爬虫实战丨基于requests爬取比特币信息并绘制价格走势图

文章目录 写在前面实验环境实验描述实验内容 写在后面 写在前面 本期内容&#xff1a;基于requests爬取比特币信息并绘制价格走势图 下载地址&#xff1a;https://download.csdn.net/download/m0_68111267/88734451 实验环境 anaconda丨pycharmpython3.11.4requests 安装r…

MySQL夯实之路-查询性能优化深入浅出

MySQL调优分析 explain&#xff1b;show status查看服务器状态信息 优化 减少子任务&#xff0c;减少子任务执行次数&#xff0c;减少子任务执行时间&#xff08;优&#xff0c;少&#xff0c;快&#xff09; 查询优化分析方法 1&#xff0e;访问了太多的行和列&#xff1…

pytorch学习笔记(十)

一、损失函数 举个例子 比如说根据Loss提供的信息知道&#xff0c;解答题太弱了&#xff0c;需要多训练训练这个模块。 Loss作用&#xff1a;1.算实际输出和目标之间的差距 2.为我们更新输出提供一定的依据&#xff08;反向传播&#xff09; 看官方文档 每个输入输出相减取…

数据库-列的类型-字符串char类型

char 和 varchar 类型 char 类型懂得都懂就是固定的字符串类型 char (maxLen) 例如 char(5) 这个长度为5 但插入数据‘a’时 是5 插入abc 也是5 即使插满固定 就像C/C语言里 char 字符数组一样 char str[64]; maxLen255 哈哈最多有255个字符多了我认为你是错误 varchar…

C++(9)——内存管理

1. 内存分类&#xff1a; 在前面的文章中&#xff0c;通常会涉及到几个名词&#xff0c;例如&#xff1a;栈、堆。这两个词所代表的便是计算机内存的一部分 。在计算机中&#xff0c;对系统的内存按照不同的使用需求进行了区分&#xff0c;大致可以分为&#xff1a;栈 、堆、数…

Pandas实战100例 | 案例 30: 应用自定义函数

案例 30: 应用自定义函数 知识点讲解 在数据处理过程中&#xff0c;有时需要对数据应用特定的逻辑&#xff0c;这时可以使用自定义函数。Pandas 的 apply 方法允许你对 DataFrame 的行或列应用一个自定义函数。 自定义函数: 你可以定义一个 Python 函数&#xff0c;该函数可…

Debezium发布历史63

原文地址&#xff1a; https://debezium.io/blog/2019/07/08/tutorial-sentry-debezium-container-images/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 将 Sentry 添加到 Debezium 容器镜像的教程 2019 年 7…

安卓11菜单实现hdmi-4K动态切换

客户要求系统实现动态hdmi-4K与普通分辨率直接热切换&#xff0c;先在菜单中做个试验&#xff0c;设置中加个切换开关&#xff0c;点击开关就可以直接切到hdmi-4K&#xff0c;这个功能实现后可以通过插拔hdmi那个状态&#xff08;sys/class/drm/card0-HDMI-A-1/status&#xff…

41k+ stars 闪电般快速的开源搜索引擎 docker安装教程

目录 1.下载 2.启动 成功示例 3.创建索引 4.插入数据 4.1下载数据 4.2插入数据 4.3查看数据 5.官方地址 1.下载 docker pull getmeili/meilisearch:latest 2.启动 mkdir -p /opt/meili_datadocker run -it --rm \-p 7700:7700 \-v /opt/meili_data:/meili_data \ge…

YOLOV7剪枝流程

YOLOV7剪枝流程 1、训练 1&#xff09;划分数据集进行训练前的准备&#xff0c;按正常的划分流程即可 2&#xff09;修改train.py文件 第一次处在参数列表里添加剪枝的参数&#xff0c;正常训练时设置为False&#xff0c;剪枝后微调时设置为True parser.add_argument(--pr…

2401d,讨论d串滑动参数

原文 因为对编译时执行的i串的兴趣,我一直在考虑搞个通用用例,而不是相关i串的用例. 滑动模板参数 请考虑以下模板: void pluto(string s)() {pragma(msg, s); } void test() {pluto!"hello"(); }因为s是编译时参数,这编译,而pragma(msg,s) 期望s为编译时值. voi…

Linux第28步_编译“正点原子的TF-A源码”

编译“正点原子的TF-A源码”&#xff0c;目的是想得到TF-A文件&#xff0c;即“tf-a-stm32mp157d-atk-trusted.stm32”。 在前27步的基础上&#xff0c;才可以学习本节内容&#xff0c;学习步骤如下&#xff1a; 1、创建“alientek_tf-a”目录&#xff1b; 2、复制正点原子的…

.【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)

概率图模型是一种用图形表示概率分布和条件依赖关系的数学模型。概率图模型可以分为两大类&#xff1a;有向图模型和无向图模型。有向图模型也叫贝叶斯网络&#xff0c;它用有向无环图表示变量之间的因果关系。无向图模型也叫马尔可夫网络&#xff0c;它用无向图表示变量之间的…

02.部署LVS-DR群集

技能展示&#xff1a; 了解LVS-DR群集的工作原理 会构建LVS-DR负载均衡群集 2.1 LVS-DR 集群 LVS-DR&#xff08; Linux Virtual Server Director Server &#xff09;工作模式&#xff0c;是生产环境中最常用的一种工作模式。 2.1.1&#xff0e;LVS-DR 工作原理 LVS-DR 模式&…

在root账号下启动 elasticsearch

最新版本的 elasticsearch 不能用root账号运行&#xff0c;但是每次启动都要切换到其他账号下面&#xff0c;也是一件很麻烦的事情。下面写个脚本&#xff0c;以指定用户运行elasticsearch 假设服务器已经装好了elasticsearch&#xff0c;并且在 /usr/local/elasticsearch-8.1…