Spring Boot - Application Events 的发布顺序_ApplicationStartingEvent

文章目录

概述

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

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

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

Spring Boot中的ApplicationStartingEvent是在应用程序启动的最早阶段触发的事件。它在应用程序上下文完全初始化之前触发,此时应用程序刚刚启动,各种初始化任务(如加载配置文件和设置环境)尚不可用。

通过监听ApplicationStartingEvent,我们可以执行一些预初始化任务,例如更新系统属性、读取外部配置、初始化日志框架等。


Code

java 复制代码
package com.artisan.event;

import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationListener;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class ApplicationStartingListener implements ApplicationListener<ApplicationStartingEvent> {

    /**
     * ApplicationStartingEvent 在应用程序启动过程的一开始,就在应用程序上下文完全初始化之前触发。
     * 此时,应用程序刚刚启动,加载配置文件、设置环境等各种初始化任务尚不可用。
     * <p>
     * <p>
     * 通过监听 ,我们可以执行预初始化任务 ApplicationStartingEvent ,例如更新系统属性、读取外部配置、初始化日志记录框架等
     * <p>
     * <p>
     * 为了处理 ApplicationStartingEvent ,我们可以通过实现 ApplicationListener 作为 ApplicationStartingEvent 泛型类型的接口来创建一个自定义事件侦听器。
     * 此侦听器可以在主应用程序类中手动注册
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        System.out.println("--------------------> Handling ApplicationStartingEvent here!");
    }
}
    

如何使用呢?

方式一:

java 复制代码
@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);

        // 当应用程序运行时,将调用 的方法 ApplicationStartingListener#onApplicationEvent()
        // 允许我们在应用程序上下文完全建立之前执行任何必要的任务。
        springApplication.addListeners(new ApplicationStartingListener());

       springApplication.run(args);
    }


}

方式二: 通过spring.factories 配置

xml 复制代码
org.springframework.context.ApplicationListener=\
com.artisan.event.ApplicationStartingListener 

当应用程序运行时,ApplicationStartingListener#onApplicationEvent()方法将被调用,从而允许我们在应用程序上下文完全建立之前执行任何必要的任务

运行日志


源码分析

首先main方法启动入口

java 复制代码
SpringApplication.run(LifeCycleApplication.class, args);

跟进去

java 复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

继续

java 复制代码
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

这里首先关注 new SpringApplication(primarySources)

new SpringApplication(primarySources)

java 复制代码
	/**
	 * 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));

通过spring.factories 配置的方式会扫描到 然后 setListeners


run

继续run

java 复制代码
// 开始启动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; // 返回应用上下文
}

我们重点看

java 复制代码
    SpringApplicationRunListeners listeners = getRunListeners(args); // 获取运行监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass); // 通知监听器启动过程开始

继续查看 starting

java 复制代码
// 用于执行与特定步骤相关的监听器和动作。
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
        Consumer<StartupStep> stepAction) {
    // 获取一个StartupStep实例,这个实例对应于指定的stepName。
    StartupStep step = this.applicationStartup.start(stepName);
    // 遍历所有的监听器,并执行动作(这些动作通常是启动监听器)。
    this.listeners.forEach(listenerAction);
    // 如果存在步骤动作(stepAction),则执行它。步骤动作可以用来添加额外的信息或执行额外的逻辑。
    if (stepAction != null) {
        stepAction.accept(step);
    }
    // 完成当前步骤。
    step.end();
}
java 复制代码
// 这个方法是在Spring Boot应用启动的时候被调用的。它接收一个可配置的启动上下文和一个主应用类(main class)。
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    // 使用doWithListeners方法来执行一个动作,这个动作是在spring.boot.application.starting这个阶段执行的。
    // 它会调用所有的SpringApplicationRunListener监听器来完成启动过程。
    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
            (step) -> {
                // 如果主应用类不为null,给当前步骤添加一个标签,标签的值是主应用类的名字。
                if (mainApplicationClass != null) {
                    step.tag("mainApplicationClass", mainApplicationClass.getName());
                }
            });
}

发布事件

java 复制代码
	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

继续

java 复制代码
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}
java 复制代码
@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);
        }
    }
}

继续

java 复制代码
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			......
		}
	}

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

java 复制代码
  @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        System.out.println("--------------------> Handling ApplicationStartingEvent here!");
    }
相关推荐
弗拉唐3 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
2401_857610034 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_4 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
天天进步20155 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
乌啼霜满天2496 小时前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn6 小时前
java入门 自定义springboot starter
java·开发语言·spring boot
Grey_fantasy6 小时前
高级编程之结构化代码
java·spring boot·spring cloud
苹果酱05677 小时前
前端面试vue篇:Vue2 和 Vue3 在设计和性能上有显著区别
java·spring boot·毕业设计·layui·课程设计
刘大浪7 小时前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis