Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent

文章目录

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

在这里插入图片描述


Pre

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent


概述

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

在 Spring Boot 中,事件发布和监听的机制是通过 ApplicationEventApplicationListener 以及事件发布者(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
     */
    @Override
    public 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转换为Properties
        Properties properties = new Properties();
        properties.putAll(propertiesMap);

        // 创建一个PropertiesPropertySource
        PropertySource<?> 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应用上下文,初始化为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; // 返回应用上下文
}

我们重点看

  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)

发布事件

	@Override
	public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(
				new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
	}

继续

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

继续

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

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

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

    }

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年最佳免费简历编辑工具,全功能完全免费使用!

随着科技的不断发展&#xff0c;求职竞争也愈发激烈。在2024年&#xff0c;如何在众多求职者中脱颖而出成为关键问题。为了帮助大家在职业生涯中取得更好的机会&#xff0c;特别推荐一款在2024年最为出色的免费简历编辑工具——芊芊简历。 1. 免费编辑功能 芊芊简历拥有直观易…

rime中州韵小狼毫 生字注音滤镜 汉字注音滤镜

在中文环境下&#xff0c;多音字是比较常见的现象。对于一些不常见的生僻字&#xff0c;或者一些用于地名&#xff0c;人名中的常见字的冷门读音&#xff0c;如果不能正确的阅读&#xff0c;例如把 荥阳 读成了 miāo yng&#xff0c;则会怡笑大方。 今天我们在rime中州韵小狼…

Leetcode349两个数组的交集(java实现,思路超清晰想学会的进来!)

今天&#xff0c;博主分享的题目是leetcode上的349两个数组的交集。题目描述如下&#xff1a; 解题思路:在这里我们分享一个做题的小技巧&#xff0c;我们拿到题如果题目描述中有判断某个集合中有没有哪个元素&#xff0c;类似这种要求的我们首先应该考虑是否可以使用哈希表。…

探索 C# 中的程序运行目录获取方法

探索 C# 中的程序运行目录获取方法 引言 在 C# 开发中&#xff0c;有时需要确定您的应用程序的运行目录。这可能是为了读取配置文件、存储日志&#xff0c;或者访问与应用程序位于同一目录的其他资源。C# 提供了几种方法来获取当前程序的运行目录。本文将探讨这些方法及其使用…

【漏洞复现】天融信TOPSEC static_convert 远程命令执行

漏洞描述 天融信TOPSEC Static_Convert存在严重的远程命令执行漏洞。攻击者通过发送精心构造的恶意请求,利用了该漏洞,成功实现在目标系统上执行任意系统命令的攻击。成功利用漏洞的攻击者可在目标系统上执行恶意操作,可能导致数据泄露、系统瘫痪或远程控制。强烈建议立即更…

google关键词分析怎么做?

想分析关键词那自然是要使用工具&#xff0c;而分析一个关键词比较看重的有两点&#xff0c;搜索量以及竞争程度。 搜索量无非就是关键词在谷歌搜索引擎被搜索的次数&#xff0c;这个数量越大&#xff0c;就证明这个关键词被人搜的越多次&#xff0c;我们要做的词&#xff0c;肯…

水产冷链物流行业零下25℃库架一体 海格里斯HEGERLS四向穿梭式冷藏冷库智能密集仓

随着国内外仓储物流整体规模和低温产品消费需求的稳步增长&#xff0c;冷链市场应用潜力不断释放。在传统“货架叉车”的方式下&#xff0c;货物、人员及机械设备不断进出&#xff0c;容易造成温度波动&#xff0c;导致冷量流失。立体冷库则以更高密度、更具成本效益的方式&…

【C++进阶06】红黑树图文详解及C++模拟实现红黑树

一、红黑树的概念及性质 1.1 红黑树的概念 AVL树用平衡因子让树达到高度平衡 红黑树可以认为是AVL树的改良 通过给每个节点标记颜色让树接近平衡 以减少树在插入节点的旋转 在每个结点新增一个存储位表示结点颜色 可以是Red或Black 通过对任何一条从根到叶子的路径上 各个结点…

go语言中的函数和方法

函数定义 1.1.1. golang函数特点&#xff1a; • 无需声明原型。• 支持不定 变参。• 支持多返回值。• 支持命名返回参数。 • 支持匿名函数和闭包。• 函数也是一种类型&#xff0c;一个函数可以赋值给变量。• 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。• 不…

PostgreSQL从小白到高手教程 - 第41讲:postgres表空间备份与恢复

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第41讲&#…

OpenCV-Python(33):SURF算法

目标 SUFR 是什么OpenCV 中的SURF 原理 学习了解过SIFT 算法后我们知道&#xff0c;它是对图像关键点进行检测和描述的&#xff0c;具有尺度不变的特性&#xff0c;但是这种算法的执行速度比较慢&#xff0c;人们需要速度更快的算法。2006年Bay,H.,Tuytelaars,T. 和Van Gool,…

盲盒小程序搭建,打造互联网电商模式

当前&#xff0c;盲盒作为年轻人的消费新选择&#xff0c;一度创下“销费奇迹”&#xff0c;深受年轻人的欢迎&#xff0c;成为了一种热门消费模式&#xff01;盲盒小程序是盲盒市场互联网时代发展下的产物&#xff0c;也是当下盲盒销售中的一种新形式。下面具体分析盲盒小程序…

从传统到智能:机器视觉检测赋能PCB行业数字化转型!

PCB板在现代电子设备中是一个重要的组成部分&#xff0c;它是用来集成各种电子元器件的信息载体。在电子领域中&#xff0c;PCB板有着广泛的应用&#xff0c;而它的质量直接影响到产品的性能。随着电子科技技术和电子制造业的发展&#xff0c;贴片元器件的体积 变小&#xff0c…

RT-Thread 中断管理接口

中断服务程序挂接 系统把用户的中断服务程序&#xff08;handler&#xff09;和指定的中断号关联起来&#xff0c;可调用如下的接口挂载一个新的中断服务程序&#xff1a; rt_isr_handler_t rt_hw_interrupt_install(int vector, rt_isr_handler_t handler, void*param, char…

重磅!2024国家自然科学基金项目指南发布

1月11日&#xff0c;国家自然科学基金委员会正式发布《2024年度国家自然科学基金项目指南 》。 相比去年的指南&#xff0c;今年有如下变动&#xff1a; 申请规定具体如下&#xff1a; 各项目申请指南&#xff1a; 来源&#xff1a;国家自然科学基金委员会&#xff0c;爱科会易…

mysql忘记root密码后怎么重置

mysql忘记root密码后重置方法【windows版本】 重置密码步骤停掉mysql服务跳过密码进入数据库在user表中重置密码使用新密码登录mysql到此&#xff0c;密码就成功修改了&#xff0c;完结&#xff0c;撒花~ 重置密码步骤 当我们忘记mysql的密码时&#xff0c;连接mysql会报这样的…

无代码DIY图像检索

软件环境准备 可参见《HuggingFists-低代码玩转LLM RAG-准备篇》中的HuggingFists安装及Milvus安装。 流程环境准备 图片准备 进入HuggingFists内置的文件系统&#xff0c;数据源->文件系统->sengee_fs_settings_201创建Image文件夹将事先准备的多张相同或不同种类的图…

GPCR蛋白一般残基编号(Generic residue numbering)

文章目录 前言定义特殊情况参考连接 前言 在相应的文章中看到对于对于描述GPCR中的序列位置&#xff0c;往往在除了在当前蛋白的氨基酸序列序号意外&#xff0c;会在右上角标注一个类似于6 x 49的编号。经查这个编号有一个统一名称&#xff1a;Generic residue numbering。本文…

TCP/IP 网络模型

TCP/IP 网络通常是由上到下分成 4 层&#xff0c;分别是应用层&#xff0c;传输层&#xff0c;网络层和网络接口层。 应用层 应用层专注于为用户提供应用功能&#xff0c;比如 HTTP、FTP、Telnet、DNS、SMTP等。我们电脑或手机使用的应用软件都是在应用层实现。应用层是不用去关…

2024年T电梯修理证考试题库及T电梯修理试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年T电梯修理证考试题库及T电梯修理试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大纲随机出的T电…