Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent
文章目录
- Pre
- 概述
- Code
- 源码分析

Pre
Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent
概述
Spring Boot 的广播机制是基于观察者模式实现的,它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦,使得应用程序中的不同组件可以独立地改变和复用逻辑,而无需直接进行通信。
在 Spring Boot 中,事件发布和监听的机制是通过 ApplicationEvent
、ApplicationListener
以及事件发布者(ApplicationEventPublisher
)来实现的。其中,ApplicationEvent 是所有自定义事件的基础,自定义事件需要继承自它。
ApplicationListener
是监听特定事件并做出响应的接口,开发者可以通过实现该接口来定义自己的监听器。事件发布者(通常由 Spring 的 ApplicationContext
担任)负责发布事件。
ApplicationEnvironmentPreparedEvent
事件在Spring Boot应用程序中非常有用。当应用程序环境准备就绪时,可以使用此事件来执行一些初始化操作,例如设置系统属性、加载配置文件、动态修改环境等。
通过监听ApplicationEnvironmentPreparedEvent
事件,我们可以在Spring Boot应用程序启动之前对环境进行一些自定义的配置和修改,以满足特定的需求。例如,可以在此事件中动态加载不同的配置文件,根据环境变量设置不同的系统属性,或者执行其他与环境相关的初始化操作
Code
package com.artisan.event;import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;import java.util.HashMap;
import java.util.Map;
import java.util.Properties;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class ApplicationEnvironmentPreparedListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {/*** ApplicationEnvironmentPreparedEvent 在应用程序环境准备时触发,此时应用程序上下文尚未初始化。* <p>* 通过侦听此事件,我们可以访问环境的属性、配置文件和配置源。* 然后,我们可以执行一些任务,例如修改属性值、添加或删除配置源、激活特定配置文件或根据环境状态应用自定义逻辑。* <p>* <p>* 为了处理事件 ApplicationEnvironmentPreparedEvent ,我们可以通过实现 ApplicationListener ApplicationEnvironmentPreparedEvent 作为泛型类型的接口来创建自定义事件侦听器。* 此侦听器可以在主应用程序类中手动注册** @param event the event to respond to*/@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {System.out.println("--------------------> Handling ApplicationEnvironmentPreparedEvent here!");ConfigurableEnvironment environment = event.getEnvironment();// 添加自定义属性创建一个Map对象Map<String, Object> propertiesMap = new HashMap<>();propertiesMap.put("myDynamicProperty", "myValue");// 将Map转换为PropertiesProperties properties = new Properties();properties.putAll(propertiesMap);// 创建一个PropertiesPropertySourcePropertySource<?> propertySource = new PropertiesPropertySource("dynamicProperties", properties);// 将PropertiesPropertySource添加到环境属性源中environment.getPropertySources().addFirst(propertySource);SpringApplication springApplication = event.getSpringApplication();springApplication.setDefaultProperties(properties);}
}
如何使用呢?
方式一:
@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);// 当我们运行 Spring Boot 应用程序时,将调用 的方法 ApplicationEnvironmentPreparedListener#onApplicationEvent() 允许我们根据需要访问和修改应用程序的环境springApplication.addListeners(new ApplicationEnvironmentPreparedListener());springApplication.run(args);}}
方式二: 通过spring.factories 配置
org.springframework.context.ApplicationListener=\
com.artisan.event.ApplicationEnvironmentPreparedListener
运行日志
源码分析
首先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; // 返回应用上下文
}
我们重点看
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
继续
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// 获取或创建环境对象ConfigurableEnvironment environment = getOrCreateEnvironment();// 使用应用程序参数配置环境configureEnvironment(environment, applicationArguments.getSourceArgs());// 将配置属性源附加到环境中ConfigurationPropertySources.attach(environment);// 通知监听器环境已经准备好listeners.environmentPrepared(bootstrapContext, environment);// 将默认属性属性源移动到环境的末尾DefaultPropertiesPropertySource.moveToEnd(environment);// 配置额外的配置文件configureAdditionalProfiles(environment);// 将环境绑定到Spring应用程序bindToSpringApplication(environment);// 如果不是自定义环境,则转换环境if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}// 再次将配置属性源附加到环境,确保所有属性源都被正确加载ConfigurationPropertySources.attach(environment);// 返回配置好的环境对象return environment;
}
重点关注: listeners.environmentPrepared(bootstrapContext, environment);
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {doWithListeners("spring.boot.application.environment-prepared",(listener) -> listener.environmentPrepared(bootstrapContext, environment));}
继续 listener.environmentPrepared(bootstrapContext, environment)
发布事件
@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));}
继续
@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(ApplicationEnvironmentPreparedEvent event) {System.out.println("--------------------> Handling ApplicationEnvironmentPreparedEvent here!");..........}
相关文章:

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent
文章目录 Pre概述Code源码分析 Pre Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent 概述 Spring Boot 的广播机制是基于观察者模式实现的,它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦&#…...
华为HCIE课堂笔记第十三章 IPv6地址配置
目录 第十三章 IPv6地址配置 13.1 IPv6地址无状态自动配置 13.1.1 RS和RA报文格式 13.1.2 RA的Flags字段 13.1.3 地址的生存周期 13.1.4 RA报文中前缀中的Flags 13.2 DHCPv6 13.2.1 DHCPV6的概念 13.2.2 DCHPv6的报文 第十三章 IPv6地址配置 13.1 IPv6地址无状态自动…...

计算机网络-VLAN间通信
之前复习了VLAN的概念以及几个接口类型。VLAN在二层可以实现广播域的划分,VLAN间可以实现二层通信,但是不能实现三层通信,需要借助其它方式。 一、概述 实际网络部署中一般会将不同IP地址段划分到不同的VLAN。同VLAN且同网段的PC之间可直接进…...

vue3的福音框架arco.design
前言: 在vue2于2023年底正式宣布不在维护,vue3使用越来越频繁的时刻,我们实现项目的辅助框架也越来越多。element, iview, antd 等经典框架继续风靡一时,不过也有很多好的框架,功能也强大,比如我们今天说的…...

BSP视频教程第29期:J1939协议栈CAN总线专题,源码框架,执行流程和应用实战解析,面向车通讯,充电桩,模组通信等(2024-01-08)
视频教程汇总帖:【学以致用,授人以渔】2024视频教程汇总,DSP第12期,ThreadX第9期,BSP驱动第29期,USB实战第5期,GUI实战第3期(2024-01-08) - STM32F429 - 硬汉嵌入式论坛 …...

Java lambda表达式如何自定义一个toList Collector
匿名类: package l8;import java.util.*; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.s…...

【hcie-cloud】【18】华为云Stack灾备服务介绍【容灾解决方案介绍、灾备方案架构介绍、管理组件灾备方案介绍、高阶云服务容灾简介、缩略词】【下】
文章目录 灾备方案概述、备份解决方案介绍容灾解决方案介绍华为云容灾解决方案概览云容灾服务云硬盘高可用服务 (VHA)VHA组网结构VHA逻辑组网架构VHA管理组件介绍VHA服务实现原理云服务器高可用服务(CSHA)CSHA物理组网架构CSHA逻辑组网架构CSHA服务组件间…...

linux建立软链接——以matlab为例
如果软件没有建立软连接,每次打开terminal就只是个黑黑的窗口,每次打开软件都要自己load一次,比较麻烦。 第一步: 在工作夹新建文档:project.cshrc 第二步: 在夹新建文档中写入:module laod m…...

ubuntu20固定串口名称
查看串口的详细信息 udevadm info --name/dev/ttyUSB0结果: P: /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.2/1-1.2:1.0/ttyUSB0/tty/ttyUSB0 N: ttyUSB0 L: 0 S: serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UAR…...

扩散模型(二)——DDIM学习笔记-大白话推导
扩散模型系列: (1)扩散模型(一)——DDPM推导笔记-大白话推导 (2)扩散模型(二)——DDIM学习笔记-大白话推导 请提前关注,后续待更新,谢谢… 写在前面: (1)建议…...

【软件测试作业_TPshop商城】农业工程学院-测试需求分析与测试计划+自动化+性能+测试用例+报告软件缺陷+测试计划+单元测试+系统测试
1测试需求分析与测试计划 1.1 被测系统简介 1.2测试需求分析 1.2.1单元测试层面的测试需求分析 1.2.2系统测试层面的测试需求分析 1.3测试计划 1.31测试范围与任务 1.3.2 测试环境 1.3.3测试进度安排 测试用例的设计2 2.1单元测试层面的测试用例设计 2.2系统测试层面的测试用例…...

屏幕截图编辑工具Snagit中文
Snagit是一款优秀的屏幕、文本和视频捕获与转换程序。它能够捕获屏幕、窗口、客户区窗口、最后一个激活的窗口或用鼠标定义的区域,并支持BMP、PCX、TIF、GIF或JPEG格式的保存。Snagit还具有自动缩放、颜色减少、单色转换、抖动等功能,并能将捕获的图像转…...

12GoF之代理模式
解决问题的思维:AOP 解决问题技术:代理技术 代理技术太麻烦,因此使用框架 Spring AOP框架(底层是代理技术:jdk动态daili,cglib) 代理模式是GoF23种设计模式之一。属于结构型设计模式。 代理…...

Unity中URP下实现能量罩(扭曲流光花纹)
文章目录 前言一、能量罩花纹1、在属性面板接收能量罩花纹纹理2、申明 纹理 和 采样器3、在顶点着色器,应用 Tilling 和 Offset4、在片元着色器,纹理采样后,与之前的结果相乘输出 二、能量罩流光1、在顶点着色器,记录原uv值2、在片…...

南京银行高管上新:“70后董事长”谢宁将上任,能否及时救场?
撰稿|行星 来源|贝多财经 2024年上市银行的首例“换帅”事件,由南京银行拉开帷幕。 1月8日,南京银行(SH:601009)发布公告称,该行2024年第一次临时股东大会选举产生了第十届董事会11名董事,同意选举谢宁担…...
K8S容器编排基本使用
Kubernetes容器编排技术基本使用 1.部署模式发展历程 物理单机 – 虚拟机(VMware)-- IAAS(基础设施即服务 比如够买jsp主机模式)-- OPENSTACK(多个机器分片使用思想)-- docker(容器化…...
PyTorch 各种池化层函数全览与用法演示
目录 torch.nn.functional子模块Pooling层详解 avg_pool1d 用法与用途 参数 注意事项 示例代码 avg_pool2d 用法与用途 参数 注意事项 示例代码 avg_pool3d 用法与用途 参数 注意事项 示例代码 max_pool1d 用法与用途 参数 注意事项 示例代码 max_pool2d…...

Redis:原理速成+项目实战——Redis实战7(优惠券秒杀+细节解决超卖、一人一单问题)
👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习 🌌上期文章:Redis:原理速成项目实战——Redis实战6(封装缓存工具(高级写法)&&缓存总…...
【刷题笔记3】
笔记3 输出小数位数控制。(自动四舍五入,不够就自动补0) double a123.456; cout<<fixed<<setprecision(2)<<a;递归题目的记录 (1):n*m的棋盘格子(n为横向的格子数…...

YOLOv8优化策略:轻量化改进 | 华为Ghostnet,超越谷歌MobileNet | CVPR2020
🚀🚀🚀本文改进:Ghost bottleneck为堆叠Ghost模块 ,与YOLOV8建立轻量C2f_GhostBottleneck 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1.Ghostnet介绍 论文: https://arxiv.org/pdf/1911.11907.…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

【51单片机】4. 模块化编程与LCD1602Debug
1. 什么是模块化编程 传统编程会将所有函数放在main.c中,如果使用的模块多,一个文件内会有很多代码,不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数声明,其他.c文…...
boost::filesystem::path文件路径使用详解和示例
boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类,封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解,包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...