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*/@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);// 在执行 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应用上下文,初始化为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.started(context);

继续

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

继续 listener.started(context)

 	@Overridepublic 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) {// 断言事件对象不能为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(ApplicationStartedEvent event) {System.out.println("--------------------> Handling ApplicationStartedEvent here!");}

在这里插入图片描述

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

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

相关文章

视频监控平台的管理员账号在所有客户端都无法登录的问题解决

目 录 一、问题描述 二、问题排查 1、看问题提示 2、看日志信息 3、问题定位 三、问题解决 1. 添加权限角色 2、添加操作用户 3、验证 一、问题描述 AS-V1000视频监控平台安装部署完成后&#xff0c;发现管理员admin不能到web客户端&#xff0c;觉…

Maven的安装和配置

国内Maven仓库之阿里云Aliyun仓库地址及设置 用过Maven的都知道Maven的方便便捷&#xff0c;但由于某些网络原因&#xff0c;访问国外的Maven仓库不便捷&#xff0c;好在阿里云搭建了国内的maven仓库。 需要使用的话&#xff0c;要在maven的settings.xml 文件里配置mirrors的子…

Github 2024-01-13 C#开源项目日报 Top8

根据Github Trendings的统计&#xff0c;今日(2024-01-13统计)共有8个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量C项目8 Redis - 内存数据库和数据结构服务器 创建周期&#xff1a;5411 天开发语言&#xff1a;C协议类型&am…

基于SSM中小型医院管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

代码随想录-刷题第五十五天

72. 编辑距离 题目链接&#xff1a;72. 编辑距离 思路&#xff1a;本题是用动规来解决的经典题目&#xff0c;这道题目看上去好像很复杂&#xff0c;但用动规可以很巧妙地算出最少编辑距离。动态规划五步曲分析&#xff1a; dp[i][j]表示以下标i-1为结尾的字符串word1&#x…

Hive基础知识(十):Hive导入数据的五种方式

1. 向表中装载数据&#xff08;Load&#xff09; 1&#xff09;语法 hive> load data [local] inpath 数据的 path[overwrite] into table student [partition (partcol1val1,…)]; &#xff08;1&#xff09;load data:表示加载数据 &#xff08;2&#xff09;local:表示…

什么是网络数据抓取?有什么好用的数据抓取工具?

一、什么是网络数据抓取 网络数据抓取&#xff08;Web Scraping&#xff09;是指采用技术手段从大量网页中提取结构化和非结构化信息&#xff0c;按照一定规则和筛选标准进行数据处理&#xff0c;并保存到结构化数据库中的过程。目前网络数据抓取采用的技术主要是对垂直搜索引…

【大数据架构】日志采集方案对比

整体架构 日志采集端 Flume Flume的设计宗旨是向Hadoop集群批量导入基于事件的海量数据。系统中最核心的角色是agent&#xff0c;Flume采集系统就是由一个个agent所连接起来形成。每一个agent相当于一个数据传递员&#xff0c;内部有三个组件&#xff1a; source: 采集源&…

【JVM的相关参数和调优】

文章目录 JVM 调优的参数类型一、标配参数二、X参数三、XX参数 JVM 调优的常用参数 JVM 调优的参数类型 一、标配参数 这类此参数在jdk的各个版本之间很少会变化&#xff0c;基本不改变 java -version&#xff0c;查看当前电脑上的jdk的版本信息 java -help&#xff0c;查看…

基于面向对象编程,C++实现单链表

链表&#xff1a;在内存空间中是非连续存储 组成&#xff1a;链表是由一个个节点组成的&#xff0c;每个节点都包含两个元素&#xff1a;数据和指针 节点头文件&#xff1a; 建立一个ListNode.h头文件 #pragma once class ListNode { public:int value;ListNode* next;Lis…

Vue:将以往的JQ页面,重构成Vue组件页面的大致思路梳理(组件化编码大致流程)

一、实现静态组件 组件要按照功能点拆分&#xff0c;命名不要与HTML元素冲突。 1、根据UI提供的原型图&#xff0c;进行结构拆分&#xff0c;拆分的粒度以是否方便给组件起名字为依据。并梳理好对应组件的层级依赖关系。 2、拆分好结构后&#xff0c;开始对应的写组件&#x…