Spring Boot - Application Events 的发布顺序_ApplicationStartedEvent

文章目录

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

在这里插入图片描述


Pre

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent


概述

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

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

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


ApplicationStartedEvent 是Spring Boot中一个重要的应用程序事件,该事件在应用程序的Spring容器刷新之后,但在调用任何应用程序和命令行运行程序之前发布。这个事件的目的是为了允许开发者能够在应用程序启动的过程中执行一些特定的逻辑。

当Spring Boot应用程序启动时,它会经历多个阶段,包括初始化SpringApplicationRunListeners,创建和配置Spring环境,以及刷新ApplicationContext。在ApplicationContext被刷新之后,但尚未调用任何CommandLineRunnerApplicationRunner之前,ApplicationStartedEvent事件被发布。

开发者可以通过实现ApplicationListener接口来监听这个事件。具体来说,他们需要创建一个类,该类实现ApplicationListener接口,并重写onApplicationEvent方法。在这个方法中,开发者可以定义当ApplicationStartedEvent事件被发布时应该执行的逻辑。
此外,Spring Boot还提供了其他几种方式在应用程序启动时执行自定义代码,例如使用CommandLineRunner接口或ApplicationRunner接口。这些接口在ApplicationStartedEvent事件之后被调用,因此也可以用于启动时的逻辑。

总之,ApplicationStartedEvent是Spring Boot中一个关键的应用程序事件,它允许开发者在应用程序启动过程中执行特定的逻辑,从而实现对应用程序启动过程的精细控制。


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
     */
    @Override
    public 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);

       // 在执行 Spring Boot 应用程序时,会调用 的方法 ApplicationStartedListener , onApplicationEvent() 使我们能够在应用程序开始时执行任何必要的附加设置或初始化任务
       springApplication.addListeners(new ApplicationStartedListener());

       springApplication.run(args);
    }


}

方式二: 通过spring.factories 配置

在这里插入图片描述

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

运行日志

在这里插入图片描述


源码分析

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

我们重点看

listeners.started(context);

继续

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


继续 listener.started(context)

 	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

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

/**
 * 发布一个事件,这个事件可以被订阅者处理。
 *
 * @param event 事件对象,不能为null
 * @param eventType 事件类型,用于类型匹配和事件处理器的选择
 */
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    // 断言事件对象不能为null
    Assert.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;
        }
    }
}

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

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

在这里插入图片描述

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

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

相关文章

linux 服务器上安装 ftp(亲测有效)

目录 1 需求2 安装 1 需求 服务器上需要安装ftp 2 安装 1 下载地址 https://developer.aliyun.com/packageSearch?wordvsftpd或者 https://rpmfind.net/linux/rpm2html/search.php?queryvsftpd2 如何判断 服务器是否安装了ftp rpm -qa | grep vsftpd出现提示则表示已安装…

什么是国密算法

国密算法是指由中国国家密码管理局发布的密码算法标准&#xff0c;旨在保障国家信息安全。目前&#xff0c;国家密码管理局已发布了一系列国产商用密码标准算法&#xff0c;包括SM1&#xff08;SCB2&#xff09;、SM2、SM3、SM4、SM7、SM9以及祖冲之密码算法&#xff08;ZUC)等…

thinkphp学习09-数据库的数据新增

单数据新增 使用 insert()方法可以向数据表添加一条数据&#xff0c;更多的字段采用默认 public function index() {$data [username > 犬夜叉,password > 123,gender > 男,email > wjl163.com,price > 999,details > 犬夜叉介绍];echo Db::name(user)-&g…

【教学类-43-18】A4最终版 20240111 数独11.0 十宫格X*Y=Z套(n=10),套用没有分割行列的A4横版模板

作品展示&#xff1a; 撑满格子的10宫格数独50%难度 50空 背景需求&#xff1a; 大4班有3位男孩做9宫格数独&#xff08;81格子&#xff0c;30%难度 24空&#xff09;非常娴熟&#xff0c;我观察他们基本都在10分钟内完成&#xff0c;其中一位男孩把九宫格题目给我看时表达自…

android 13.0 Launcher3长按app弹窗设置为圆角背景功能实现二

1.前言 在13.0的系统ROM定制化开发中,在进行一些Launcher3的定制化开发中,在使用app的弹窗的功能时,会弹出应用信息和 微件之类的内容,所以在定制需求中,需要默认设置为圆角背景,接下来就来分析下相关功能的实现如图: 2.Launcher3长按app弹窗设置为圆角背景功能实现二的…

【目标检测】评价指标:混淆矩阵概念及其计算方法(yolo源码)

本篇文章首先介绍目标检测任务中的评价指标混淆矩阵的概念&#xff0c;然后介绍其在yolo源码中的实现方法。 目标检测中的评价指标&#xff1a; mAP概念及其计算方法(yolo源码/pycocotools) 混淆矩阵概念及其计算方法(yolo源码) 本文目录 1 概念2 计算方法 1 概念 在分类任务中…

rime中州韵小狼毫 汉语拼音输入方案

在word中&#xff0c;我们可以轻易的给汉字加上拼音&#xff0c;如下&#x1f447;&#xff1a; 但是&#xff0c;如何单独的输入拼音呢&#xff1f;例如输入 pīn yīn, 再如 zhōng guō。今天我们分享一个使用rime中州韵小狼毫须鼠管输入法配置的输入汉语拼音的输入方案。功…

WPS - 表格虚线变成实线解决方案(Office 同上)

1、选中表格区域&#xff0c;在表格中选中需要调整为实线的表格区域 2、点击设置单元格格式&#xff0c;鼠标进行右击并点击设置单元格格式选项 3、选择实线&#xff0c;在单元格格式下的边框&#xff0c;调整到实线 4、设置为实线&#xff0c;即可将表格的虚线设置为实线

(ros2)gazebo颜色设置

在gazebo当中不用再设置颜色了&#xff0c;因为完全可以使用urdf的设置 <robot name"base" xmlns:xacro"http://wiki.ros.org/wiki/xacro"><xacro:property name"PI" value"3.1415926"/><!--定义一个变量PI&#xff0…

研发日记,Matlab/Simulink避坑指南(三)——向上取整Bug

文章目录 前言 背景 问题 排查 解决 总结 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南&#xff08;一&#xff09;——Data Store Memory模块执行时序Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(二)——非对称数据溢出Bug》 背景 在一个嵌入式软…

vi/vim 编辑器 --基本命令

1 vi/vim编辑器介绍 vi 是visual interface 的简称&#xff0c;是Linux中最经典的文本编辑器 vim是vi的加强版。兼容了vi的所有指令&#xff0c;不仅能编辑文本&#xff0c;而且具有shell程序编辑的功能&#xff0c;可以通过不同颜色的字体辨别语法的正确性&#xff0c;极大…

七麦数据js逆向(补环境版)

本文目标地址如下&#xff0c;使用base64解码获得 aHR0cHM6Ly93d3cucWltYWkuY24vcmFuay9tYXJrZXRSYW5rL21hcmtldC82L2NhdGVnb3J5LzUvY29sbGVjdGlvbi9hbGwvZGF0ZS8yMDI0LTAxLTEy 本文逆向破解分为扣代码版和补环境版&#xff0c;扣代码版请看专栏另一篇文章 废话不多说了&#…

Docker 安装以及加速器配置

通常我们因为安装docker出现许多错误&#xff0c;使用解压版安装方便快捷&#xff0c;并且增加加速器的配置&#xff0c;以及可视化界面的配置&#xff0c;让我们的成长更近了一步 1. 虚拟机网络配置 虚拟机使用nat模式&#xff0c;配置ens33如下&#xff1a; TYPEEthernet P…

15 个适用于 Windows 的 PDF 解锁工具大全

PDF&#xff08;便携式文件格式&#xff09;是一种文件格式&#xff0c;用于呈现和交换任何独立于任何软件、硬件或操作系统的可靠访问的文档。Adobe 发明了它&#xff0c;但现在它是由国际标准化组织 (ISO) 维护的开放标准。因此&#xff0c;您现在必须了解这些 PDF 解锁工具。…

微软等开源评估ChatGPT、Phi、Llma等,统一测试平台

微软亚洲研究院、中国科学院自动化研究所、中国科学技术大学和卡内基梅隆大学联合开源了&#xff0c;用于评估、分析大语言模型的统一测试平台——PromptBench。 Prompt Bench支持目前主流的开源、闭源大语言模型&#xff0c;例如&#xff0c;ChatGPT、GPT-4、Phi、Llma1/2、G…

PagedAttention: from interface to kernal

1 Overview PagedAttention灵感来自于操作系统中虚拟内存和分页的经典思想&#xff0c;它可以允许在非连续空间立存储连续的KV张量。具体来说&#xff0c;PagedAttention把每个序列的KV缓存进行了分块&#xff0c;每个块包含固定长度的token&#xff0c;而在计算attention时可…

Fabric2.2:在有系统通道的情况下搭建应用通道

写在最前 在使用Fabric-SDK-Go1.0.0操作Fabric网络时遇到了bug。Fabric-SDK-GO的当前版本没有办法在没有系统通道的情况下创建应用通道&#xff0c;而Fabric的最新几个版本允许在没有系统通道的情况下搭建应用通道。为了解决这个矛盾并使用Fabric-SDK-GO完成后续的项目开发&…

谷歌aab包在Android 14闪退而apk没问题(targetsdk 34)

问题原因 Unity应用(target SDK 34)上线到GooglePlay&#xff0c;有用户反馈fold5设备上&#xff08;Android14系统&#xff09;疯狂闪退&#xff0c;经测试&#xff0c;在小米手机Android14系统的版本复现成功了&#xff0c;奇怪的是apk直接安装没问题&#xff0c;而打包成aa…

MacOS环境下Kali Linux安装及使用指导

Kali Linux是一个开源的、基于Debian的Linux发行版&#xff0c;面向各种信息安全任务&#xff0c;如渗透测试、安全研究、计算机取证和逆向工程&#xff0c;是最先进的渗透测试发行版&#xff0c;它的前身是BackTrack。 1. 我们为什么要用Kali Linux 由于Kali Linux具有以下特…

Typora上传图片失败PicGo

起初我是在Typora中点击一键上传图片&#xff0c;结果如下&#xff0c;报错。可是我找了半天也没错啊。 最后发现原来是一个图片的命名问题&#xff0c;名字太过于复杂&#xff0c;PicGo识别不出&#xff0c;一个图片报错导致其它也上传不了。 我把它复制到其它文件夹之后&…