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> {
    @Override
    public 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> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 第一个操作的逻辑
    }
}

@Order(2)
public class SecondApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public 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
     */
    @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);

       // 当应用程序完全初始化并准备就绪时,将调用 的方法 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应用上下文,初始化为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.running(context); // 通知监听器运行中

继续

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


继续 listener.running(context)

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

在这里插入图片描述

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

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

相关文章

每日一练:LeeCode-111. 二叉树的最小深度【二叉树】

本文是力扣LeeCode-111. 二叉树的最小深度 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子…

Java异常处理--异常处理的方式1:try-catch-finally

文章目录 一、异常处理概述二、方式1&#xff1a;捕获异常&#xff08;try-catch-finally&#xff09;&#xff08;1&#xff09;抓抛模型&#xff08;2&#xff09;try-catch-finally基本格式1、基本语法2、整体执行过程3、try和catch3.1 try3.2 catch (Exceptiontype e) &…

掌握 gRPC 和 RPC 的关键区别

一、远程过程调用协议简介 1、RPC 的本质 首先&#xff0c;我们探讨一下什么是 RPC。RPC&#xff0c;缩写为 Remote Procedure Call Protocol&#xff0c;直译来看就是远程过程调用协议。 讲得通俗一些&#xff1a; RPC 是一种通信机制RPC 实现了客户端/服务器通信模型 官…

【数据集处理】FFHQ如何进行人脸对齐,Aligned and cropped images at 1024×1024

什么是人脸对齐&#xff1f; 人脸对齐是一种图像处理技术&#xff0c;旨在将图像中的人脸部分对齐到一个标准位置或形状。在许多情况下&#xff0c;这通常涉及将眼睛、鼻子和嘴巴等关键点对齐到特定的位置。通过这种方式&#xff0c;所有的人脸图像可以有一个一致的方向和尺寸…

【JVM的相关参数和调优】

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

阴盘奇门八字排盘马星位置计算方法php代码

如下位置&#xff0c;马星的四个位置。 计算方法&#xff1a; 1。先根据出生年月日&#xff0c;计算得八字四柱。比如 2024年01月09日&#xff0c;四柱为 其中时柱地支为“申” 2。然后根据以下对应的数组&#xff0c;来找到id号&#xff0c;即马星位置。 根据下表来找到&am…

开机自启动android app

Android App开机自启动_android 开机自启动-CSDN博客 注意权限问题&#xff1a; 第二种实现方式&#xff1a;系统桌面应用 问&#xff1a;android的系统桌面应用启动是什么&#xff1a; 答&#xff1a; Android 系统桌面应用是指用户在设备主屏幕上看到的默认启动界面&…

What does `HandlerInterceptor` do?

HandlerInterceptor 是 SpringMVC 中的一个接口&#xff0c;在SpringMVC应用中它提供了一种实现应用级拦截器的机制。 第1步&#xff1a;引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web<…

利用 Azure Data Bricks的免费资源学习云上大数据

在这个数据驱动的时代&#xff0c;大数据和云计算已成为推动技术创新和商业智能的关键因素。Azure Databricks&#xff0c;作为一个先进的云平台&#xff0c;为那些渴望深入了解和掌握这些技术的人们提供了一个理想的学习环境。我们这里将利用 Azure Databricks 的免费资源&…

C语言进阶指南(22)——文件管理函数

欢迎来到博主的专栏——C语言进阶指南 博主id&#xff1a;代码小豪 文章目录 一、文件输入输出函数fwritefread 二、文件定位函数文件位置fseekftellrewind 三、文件缓冲区fflush 一、文件输入输出函数 这些函数用于文件流&#xff0c;主要功能是将一连串的数据输出或输入&am…

python24.1.13for循环

对列表、字典、字符串等进行迭代 range

关系型数据库和MySQL概述

关系型数据库概述 数据持久化 - 将数据保存到能够长久保存数据的存储介质中,在掉电的情况下数据也不会丢失。数据库发展史 - 网状数据库、层次数据库、关系数据库、NoSQL 数据库、NewSQL 数据库。1970年,IBM的研究员E.F.Codd在_Communication of the ACM_上发表了名为_A Rela…

可盐可甜的红色马甲背心

膨体棉腈面料不易皱&#xff0c;搭配阿兰花菱形镂空设计 真的绝绝子&#xff0c;红色吸睛又美观 随便搭配一件衬衫去穿&#xff0c;自带文艺气息 氛围感直接拉满 出街拍照很出片&#xff0c;时髦又气质 女孩子的甜美&#xff0c;温柔等都可以突显 有喜欢的可以尝试一下哟…

Java课程设计团队博客 —— 基于网页的时间管理系统

博客目录 1.项目简介2.项目采用的技术3.功能需求分析4.项目亮点5.主要功能截图6.Git地址7.总结 Java团队博客分工 姓名职务负责模块个人博客孙岚组长 资源文件路径和tomcat服务器的相关配置。 前端的页面设计与逻辑实现的代码编写。 Servlet前后端数据交互的编写。 用户登录和…

数据结构实战:变位词侦测

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;逐个比较法1、编写源程序2、代码解释说明&#xff08;1&#xff09;函数逻辑解释&#xff08;2&#xff09;主程序部分 3、运行程序&#xff0c;查看结果4、计算时间复杂度 &#xff08;二&#xff09;排序比较法1…

windows server 2012、2019服务器定时重启

手动设置定时任务 1.开始菜单&#xff0c;找到“计划任务程序”; 如果无法创建基本任务的话&#xff0c;可能是系统中的“Task Scheduler”服务没有启动&#xff0c;你可在运行中键入“ services.msc”&#xff0c;查看“Task Scheduler”服务是否被设置成了“已禁用”&#x…

一个个人博客应该怎么学?

一个个人博客应该怎么学&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;复&…

哪个牌子的护眼台灯适合学生?2024护眼台灯推荐

不知道各位父母对孩子的视力健康有没有关注&#xff0c;我国儿童青少年的近视率高达52.7%&#xff0c;也就是说&#xff0c;平均是个儿童中就有五个儿童存在视力问题&#xff0c;而且近视发生年龄提前至3到7岁。作为一名眼部护理博主&#xff0c;孩子从小看书、看屏幕起&#x…

10分钟快速搭建个人博客、文档网站!

本文来分享 8 个现代化前端工具&#xff0c;帮你快速生成个人博客、文档网站&#xff01; VitePress VitePress 是一款静态站点生成器&#xff0c;专为构建快速、以内容为中心的网站而设计。简而言之&#xff0c;VitePress 获取用 Markdown 编写的源内容&#xff0c;为其应用…

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

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