当前位置: 首页 > news >正文

SpringBoot源码解析(三):启动开始阶段

SpringBoot源码系列文章

SpringBoot源码解析(一):SpringApplication构造方法

SpringBoot源码解析(二):引导上下文DefaultBootstrapContext

SpringBoot源码解析(三):启动开始阶段


目录

  • 前言
  • 一、入口
  • 二、SpringApplicationRunListener
    • 1、作用及触发时机
    • 2、示例
  • 三、事件和广播器
    • 1、事件SpringApplicationEvent
      • 1.1、介绍
        • 1.1.1、EventObject
        • 1.1.2、ApplicationEvent
        • 1.1.3、SpringApplicationEvent
      • 1.2、常见的子事件
    • 2、广播器ApplicationEventMulticaster
      • 2.1、ApplicationEventMulticaster
      • 2.2、AbstractApplicationEventMulticaster
      • 2.3、SimpleApplicationEventMulticaster
  • 四、启动方法
    • 1、广播事件到应用监听器
    • 2、根据事件类型匹配监听器
      • 2.1、实现ApplicationListener<指定事件类型>
      • 2.2、实现GenericApplicationListener
    • 3、匹配到的监听器
  • 总结

前言

  前文深入解析了引导上下文DefaultBootstrapContext作为组件存储容器的角色,接下来将进入启动开始阶段的内容。

SpringBoot版本2.7.18SpringApplication的run方法的执行逻辑如下,本文将详细介绍第二小节:获取启动监听器,调用启动开始方法

// SpringApplication类方法
public ConfigurableApplicationContext run(String... args) {// 记录应用启动的开始时间long startTime = System.nanoTime();// 1.创建引导上下文,用于管理应用启动时的依赖和资源DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;// 配置无头模式属性,以支持在无图形环境下运行// 将系统属性 java.awt.headless 设置为 trueconfigureHeadlessProperty();// 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑SpringApplicationRunListeners listeners = getRunListeners(args);// 启动开始方法(发布开始事件、通知应用监听器ApplicationListener)listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 4.解析应用参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 5.准备应用环境,包括读取配置文件和设置环境变量ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置是否忽略 BeanInfo,以加快启动速度configureIgnoreBeanInfo(environment);// 6.打印启动BannerBanner printedBanner = printBanner(environment);// 7.创建应用程序上下文context = createApplicationContext();// 设置应用启动的上下文,用于监控和管理启动过程context.setApplicationStartup(this.applicationStartup);// 8.准备应用上下文,包括加载配置、添加 Bean 等prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 9.刷新上下文,完成 Bean 的加载和依赖注入refreshContext(context);// 10.刷新后的一些操作,如事件发布等afterRefresh(context, applicationArguments);// 计算启动应用程序的时间,并记录日志Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 11.通知监听器应用启动完成listeners.started(context, timeTakenToStartup);// 12.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑callRunners(context, applicationArguments);}catch (Throwable ex) {// 13.处理启动过程中发生的异常,并通知监听器handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {// 14.计算应用启动完成至准备就绪的时间,并通知监听器Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {// 处理准备就绪过程中发生的异常handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}// 返回已启动并准备就绪的应用上下文return context;
}

一、入口

  • 主要分为两个步骤,先获取Spring应用启动监听器,再调用启动方法
// 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动方法(发布开始事件、通知应用监听器ApplicationListener)
listeners.starting(bootstrapContext, this.mainApplicationClass);
  • 获取启动监听器步骤就是从spring.factories文件获取SpringApplicationRunListener实现类(SpringBoot源码解析(一):启动流程之SpringApplication构造方法有详细介绍)
    • 之前实例化上下文初始化、应用监听器都通过调用无参构造函数来创建类的实例
    • 但是应用启动监听器通过SpringApplication.class, String[].class这两参数的构造来创建实例
  • ApplicationStartup作用提供启动性能的监控和分析工具,只有导入固定依赖才会生效,暂不研究
  • 启动方法核心内容就是遍历调用SpringApplicationRunListener实现类的starting方法
// SpringApplication类方法
private SpringApplicationRunListeners getRunListeners(String[] args) {// 这两个参数就是获取SpringApplicationRunListener实现类构造函数的参数Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}// SpringApplicationRunListeners类属性
class SpringApplicationRunListeners {private final Log log;// 存储所有SpringApplicationRunListener应用启动监听器的集合private final List<SpringApplicationRunListener> listeners;private final ApplicationStartup applicationStartup;...// 启动方法void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {doWithListeners("spring.boot.application.starting", // 遍历每个监听器并执行starting方法(listener) -> listener.starting(bootstrapContext),// 为启动步骤打标签,包含mainApplicationClass信息(若不为空)(step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}});}...// 使用给定的监听器操作和步骤操作,遍历所有监听器并执行相应操作private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,Consumer<StartupStep> stepAction) {// 启动一个新的启动步骤,用于性能分析StartupStep step = this.applicationStartup.start(stepName);// 对每个监听器执行指定的动作this.listeners.forEach(listenerAction);// 如果存在步骤操作,则执行该操作if (stepAction != null) {stepAction.accept(step);}// 结束启动步骤step.end();}
}
  • 在spring-boot-2.7.18.jar下的spring.factories文件下只找到EventPublishingRunListener这一个应用启动监听器

在这里插入图片描述

二、SpringApplicationRunListener

1、作用及触发时机

  SpringApplicationRunListener是SpringBoot框架中的一个接口,用于监听Spring应用程序启动过程的生命周期事件。通过实现该接口,可以在应用启动的各个阶段执行自定义逻辑

public interface SpringApplicationRunListener {/*** 在应用程序启动开始时调用* @param bootstrapContext 提供引导上下文,可在应用程序启动期间存储和共享数据。*/default void starting(ConfigurableBootstrapContext bootstrapContext) {}/*** 当环境准备好时调用,可以在此阶段访问和修改应用程序环境。* @param bootstrapContext 提供引导上下文。* @param environment 配置的环境对象,可用于访问和自定义环境属性。*/default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,ConfigurableEnvironment environment) {}/*** 当 ApplicationContext 已创建但还未刷新时调用,可以对应用上下文进行进一步配置。* @param context Spring 应用程序上下文,提供 Bean 工厂等信息。*/default void contextPrepared(ConfigurableApplicationContext context) {}/*** 当 ApplicationContext 加载完成并已注册所有 Bean 后调用。* 此时,Bean 已加载,但未初始化。* @param context Spring 应用程序上下文。*/default void contextLoaded(ConfigurableApplicationContext context) {}/*** 应用启动完成后调用,表示应用程序已启动并处于活动状态。* @param context Spring 应用程序上下文。* @param timeTaken 应用程序启动所花费的时间。*/default void started(ConfigurableApplicationContext context, Duration timeTaken) {started(context);}/*** 这是 `started` 的过时版本,仅为了兼容性保留。* 应用启动完成后调用* @param context Spring 应用程序上下文。* @deprecated 使用 {@link #started(ConfigurableApplicationContext, Duration)} 替代*/@Deprecateddefault void started(ConfigurableApplicationContext context) {}/*** 当应用程序完全准备好可以接收请求时调用* 此方法在 ApplicationRunner 和 CommandLineRunner 执行完毕后触发* @param context Spring 应用程序上下文* @param timeTaken 应用程序启动所花费的时间*/default void ready(ConfigurableApplicationContext context, Duration timeTaken) {running(context);}/*** 这是 `ready` 的过时版本,仅为了兼容性保留。* 当应用程序完全启动时调用* @param context Spring 应用程序上下文。* @deprecated 使用 {@link #ready(ConfigurableApplicationContext, Duration)} 替代。*/@Deprecateddefault void running(ConfigurableApplicationContext context) {}/*** 在应用程序启动失败时调用,用于执行失败时的处理逻辑,比如记录错误日志或资源清理。* @param context Spring 应用程序上下文。* @param exception 导致启动失败的异常。*/default void failed(ConfigurableApplicationContext context, Throwable exception) {}
}

  在SpringBoot应用启动过程的关键阶段触发,包括starting(启动开始)environmentPrepared(环境准备完成)contextPrepared(上下文准备完成)contextLoaded(上下文加载完成)started(启动完成)ready(应用准备就绪) failed(启动失败),以便在每个阶段执行自定义逻辑。

2、示例

下面是一个简单的 SpringApplicationRunListener 实现示例:

SpringBoot应用启动监听器需要SpringApplication和String[]参数,以便访问应用上下文和传递启动时的命令行参数,从而实现对启动事件的定制和响应。所以自定义的启动监听器也需要一个标准的构造函数,才能统一的创建SpringApplicationRunListener实例。

public class MyApplicationRunListener implements SpringApplicationRunListener {public MyApplicationRunListener(SpringApplication application, String[] args) {// 构造函数必须包含 SpringApplication 和 String[] 参数}@Overridepublic void starting() {System.out.println("应用启动开始...");}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {System.out.println("环境已准备好...");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("ApplicationContext 已准备完成...");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("ApplicationContext 已加载...");}@Overridepublic void started(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("应用已启动...");}@Overridepublic void ready(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("应用正在运行...");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("应用启动失败:" + exception.getMessage());}
}

需要在META-INF/spring.factories文件中进行注册:

org.springframework.boot.SpringApplicationRunListener=com.example.MyApplicationRunListener

启动服务

在这里插入图片描述

  可以看到除了异常启动失败的触发,一共有6个触发的地方。本文就只介绍启动开始阶段触发的操作。

三、事件和广播器

  在进入启动方法之前,需要先了解下事件和广播器。

1、事件SpringApplicationEvent

1.1、介绍

  SpringApplicationEvent是SpringBoot框架中的一个重要概念,属于事件发布/订阅机制的一部分。它允许应用程序在特定事件发生时发布事件,其他组件可以订阅这些事件并做出相应的处理。这种机制提高了系统的解耦性和可扩展性。

类图如下:

在这里插入图片描述

1.1.1、EventObject
  • EventObject是Java标准库中的类,是所有事件的通用基类
  • source属性表示事件的来源对象
    • 帮助监听器识别引发事件的对象
    • 支持事件的上下文传递,使事件监听器可以基于来源执行不同逻辑
    • source使EventObject成为一个通用的事件基类,支持多种事件类型的实现和处理
// 所有事件状态对象的根类。所有的事件对象都应从此类派生
public class EventObject implements Serializable {private static final long serialVersionUID = 5516075349620653480L;// 事件最初发生的对象。使用 transient 修饰符表明该字段不会被序列化。protected transient Object source;// 构造一个原型事件public EventObject(Object source) {// 检查 source 是否为 null,避免不合法的事件源if (source == null)throw new IllegalArgumentException("null source");// 设置事件的源对象this.source = source;}// 获取事件最初发生的对象public Object getSource() {return source;}// 返回该EventObject的字符串表示形式(包括类的名称和事件源信息)public String toString() {return getClass().getName() + "[source=" + source + "]";}
}
1.1.2、ApplicationEvent
  • ApplicationEvent是所有Spring应用事件的基类,用于定义基础属性和行为
  • timestamp表示事件发生的时间
// 抽象类,应用事件的基类。所有应用事件都应扩展此类
public abstract class ApplicationEvent extends EventObject {private static final long serialVersionUID = 7099057708183571937L;// 事件发生的系统时间(时间戳)private final long timestamp;// 构造public ApplicationEvent(Object source) {// 调用父类 EventObject 的构造函数,传递事件源super(source);// 设置事件的时间戳,记录事件创建时的系统时间this.timestamp = System.currentTimeMillis();}// 此构造函数通常用于测试场景public ApplicationEvent(Object source, Clock clock) {// 调用父类 EventObject 的构造函数,传递事件源super(source);// 设置时间戳为由指定 Clock 提供的时间this.timestamp = clock.millis();}// 返回事件发生的时间public final long getTimestamp() {return this.timestamp;}
}
1.1.3、SpringApplicationEvent
  • Spring应用事件的抽象基类,用于封装Spring应用程序启动时的事件信息
  • 此类继承自ApplicationEvent,并增加了命令行参数信息
public abstract class SpringApplicationEvent extends ApplicationEvent {// 启动应用时传递的命令行参数private final String[] args;// 构造一个新的 SpringApplicationEventpublic SpringApplicationEvent(SpringApplication application, String[] args) {// 调用父类 ApplicationEvent 的构造方法,设置事件源为 SpringApplication 实例super(application);this.args = args;}// 获取触发此事件的 SpringApplication 实例public SpringApplication getSpringApplication() {// 将源对象强制转换为 SpringApplication 类型并返回return (SpringApplication) getSource();}// 获取启动应用时传递的命令行参数public final String[] getArgs() {return this.args;}
}

1.2、常见的子事件

  1. ApplicationStartingEvent
    • 作用:这是SpringBoot事件机制的第一个事件,通常用于在应用启动时初始化资源或配置项
    • 触发时机:在SpringBoot应用开始启动时发布
  2. ApplicationEnvironmentPreparedEvent
    • 作用:此事件表明应用的环境已准备就绪,但上下文还没有创建。它常被用来处理与环境相关的配置和属性
    • 触发时机:在Environment准备完成之后,ApplicationContext创建之前发布
  3. ApplicationContextInitializedEvent
    • 作用:该事件允许在ApplicationContext初始化后进行一些处理操作,比如为上下文设置额外的初始化参数
    • 触发时机:在ApplicationContext初始化完成后发布
  4. ApplicationPreparedEvent
    • 作用:此时应用的上下文已完全加载,可以在此事件中添加Bean或进行一些自定义配置
    • 触发时机:在ApplicationContext创建并加载完成后,但还未刷新前发布
  5. ApplicationStartedEvent
    • 作用:这是应用启动完成的标志,可以在此事件中处理启动后需要执行的任务
    • 触发时机:在应用完全启动且ApplicationContext已刷新之后发布
  6. ApplicationReadyEvent
    • 作用:用于表明应用已完全准备好,可以接受请求。通常用于启动后执行一次的任务,比如预加载某些资源
    • 触发时机:在应用启动并初始化完成,准备处理请求时发布
  7. ApplicationFailedEvent
    • 作用:用于处理启动失败的情况,可以在此事件中进行异常处理、资源清理或记录日志等
    • 触发时机:在启动过程中遇到异常导致启动失败时发布

2、广播器ApplicationEventMulticaster

  ApplicationEventMulticaster的核心功能是管理监听器(ApplicationListener)的注册和注销,以及将事件广播给合适的监听器

类图如下:默认唯一的抽象子类和实现类

在这里插入图片描述

2.1、ApplicationEventMulticaster

  • 此接口用于管理多个应用监听器ApplicationListener,并将事件发布给它们
  • 此接口定义了添加移除广播事件给注册的监听器的方法
  • 监听器可以通过实例或Bean名称进行注册,并可以基于自定义的条件来移除
public interface ApplicationEventMulticaster {// 添加一个监听器实例,以接收所有事件通知void addApplicationListener(ApplicationListener<?> listener);// 通过指定Bean名称添加一个监听器Bean,以接收所有事件通知void addApplicationListenerBean(String listenerBeanName);// 从通知列表中移除一个监听器void removeApplicationListener(ApplicationListener<?> listener);// 通过指定 Bean 名称,从通知列表中移除一个监听器 Beanvoid removeApplicationListenerBean(String listenerBeanName);// 从已注册的实例集合中移除所有匹配的监听器(predicate是一个条件过滤器)void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);// 从已注册的监听器Bean名称集合中移除所有匹配的监听器 Bean(predicate是一个条件过滤器)void removeApplicationListenerBeans(Predicate<String> predicate);// 移除此广播器注册的所有监听器void removeAllListeners();// 将给定的应用事件广播到合适的监听器void multicastEvent(ApplicationEvent event);// 将给定的应用事件广播到合适的监听器,eventType为事件的类型void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

2.2、AbstractApplicationEventMulticaster

  • ApplicationEventMulticaster接口的抽象实现,提供基础的监听器注册功能
  • defaultRetriever:用于直接管理监听器列表,支持实时更新,保证监听器信息的准确性
  • retrieverCache:提供缓存机制,通过事件类型和源类型匹配缓存的监听器列表,提高性能;每当监听器更新时被清空,以确保缓存数据的一致性
public abstract class AbstractApplicationEventMulticasterimplements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {// 默认监听器检索器private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();// 用于缓存监听器检索的缓存Mapfinal Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);...// 添加监听器@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {synchronized (this.defaultRetriever) {Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);if (singletonTarget instanceof ApplicationListener) {this.defaultRetriever.applicationListeners.remove(singletonTarget);}this.defaultRetriever.applicationListeners.add(listener);this.retrieverCache.clear();}}// 添加监听器Bean@Overridepublic void addApplicationListenerBean(String listenerBeanName) {synchronized (this.defaultRetriever) {this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);this.retrieverCache.clear();}}// 省略类似添加和移除方法...// defaultRetriever持有所有注册的监听器集合private class DefaultListenerRetriever {// 应用程序监听器的集合,存储直接注册的监听器实例public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();// 应用程序监听器的名称集合,存储监听器bean的名称,用于延迟加载public final Set<String> applicationListenerBeans = new LinkedHashSet<>();...}// retrieverCache持有所有注册的监听器集合private class CachedListenerRetriever {// 应用程序监听器的集合@Nullablepublic volatile Set<ApplicationListener<?>> applicationListeners;// 应用程序监听器的名称集合@Nullablepublic volatile Set<String> applicationListenerBeans;...}
}

2.3、SimpleApplicationEventMulticaster

  • 将所有事件广播到所有已注册的监听器,由监听器来决定是否忽略它们不感兴趣的事件
  • 默认情况下,所有监听器都在调用线程中同步执行,可以设置执行器使用线程池来处理
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {// 任务执行器,用于异步执行监听器,默认为空(同步执行)@Nullableprivate Executor taskExecutor;// 错误处理器,用于处理监听器抛出的异常,默认为空@Nullableprivate ErrorHandler errorHandler;// 懒加载的日志记录器,用于在需要时记录日志@Nullableprivate volatile Log lazyLogger;// 创建一个新的 SimpleApplicationEventMulticaster 实例public SimpleApplicationEventMulticaster() {}// 创建SimpleApplicationEventMulticaster时候// 设置一个异步任务执行器,就可以实现多线程执行了public void setTaskExecutor(@Nullable Executor taskExecutor) {this.taskExecutor = taskExecutor;}...// 将给定的应用事件广播到合适的监听器(核心方法)@Overridepublic void multicastEvent(ApplicationEvent event) {multicastEvent(event, resolveDefaultEventType(event));}@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();// 获取符合条件的所有监听器并进行事件广播(包括匹配合适的监听器)for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {// 如果有任务执行器,则使用执行器异步执行监听器executor.execute(() -> invokeListener(listener, event));} else {// 否则直接同步调用监听器invokeListener(listener, event);}}}// 调用给定的监听器并传递给它给定的事件protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {doInvokeListener(listener, event);} catch (Throwable err) {// 使用错误处理器处理异常errorHandler.handleError(err);}} else {doInvokeListener(listener, event);}}@SuppressWarnings({"rawtypes", "unchecked"})private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {// 调用监听器的核心方法listener.onApplicationEvent(event);} catch (ClassCastException ex) {...}}
}

四、启动方法

  启动方法实际就是遍历调用应用启动监听器SpringApplicationRunListener实现类(只有一个EventPublishingRunListener)的starting方法。

1、广播事件到应用监听器

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {// SpringApplication 对象,用于访问应用的配置信息和监听器private final SpringApplication application;// 启动参数private final String[] args;// 初始事件广播器,用于在启动阶段广播事件private final SimpleApplicationEventMulticaster initialMulticaster;/*** 初始化一个事件广播器,并将所有应用监听器添加到广播器中。*/public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;// 初始化一个事件广播器this.initialMulticaster = new SimpleApplicationEventMulticaster();// 将 SpringApplication 中的所有监听器添加到初始广播器中for (ApplicationListener<?> listener : application.getListeners()) {// 实际调用AbstractApplicationEventMulticaster的添加方法this.initialMulticaster.addApplicationListener(listener);}}// 返回该监听器的执行顺序。数字越小,优先级越高@Overridepublic int getOrder() {return 0; // 优先级最高}// 在应用启动初期调用@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {// 将应用开始事件广播到合适的监听器this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));}// 省略其他阶段的代码,后面走到对应流程在看具体操作...
}
  • 接下来看下事件广播器SimpleApplicationEventMulticaster的广播方法multicastEvent
  • 对于事件匹配监听器的方法在每个监听器中有定义

在这里插入图片描述

// 一个处理应用事件的监听器接口。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {// 响应应用事件的方法。当发布指定类型的事件时,该方法会被调用,// 以便执行相应的业务逻辑。void onApplicationEvent(E event);
}

2、根据事件类型匹配监听器

  • 匹配监听器方法getApplicationListeners,会遍历匹配每个监听器,直接定位核心方法
  • 检查监听器是否支持指定的事件类型来源类型
    • smartListener.supportsEventType(eventType): 验证监听器是否支持指定的事件类型
    • smartListener.supportsSourceType(sourceType): 验证监听器是否支持指定的事件来源类型
// AbstractApplicationEventMulticaster类方法
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {// 检查监听器是否为 GenericApplicationListener 类型// 如果是,则直接使用;如果不是,则将其包装为 GenericApplicationListenerAdapterGenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));// 检查监听器是否支持指定的事件类型和来源类型// smartListener.supportsEventType(eventType): 验证监听器是否支持指定的事件类型// smartListener.supportsSourceType(sourceType): 验证监听器是否支持指定的事件来源类型return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

  监听器分为两种形式,实现ApplicationListener<指定事件类型>或实现GenericApplicationListener(通过supportsEventType方法-支持事件类型和supportsSourceType方法-支持事件来源)。

在这里插入图片描述

2.1、实现ApplicationListener<指定事件类型>

  如果监听器是普通方式实现ApplicationListener<指定事件类型>,那么会将监听器包装为GenericApplicationListenerAdapter,核心内容就是解析出指定事件类型的实际类型

在这里插入图片描述

验证监听器是否支持指定的事件类型

  • GenericApplicationListenerAdapter实现了GenericApplicationListener的支持事件类型方法supportsEventType
  • isAssignableFrom方表示当前类是否可以表示为指定类的超类或接口

在这里插入图片描述

验证监听器是否支持指定的事件来源类型

  • 如果是常规ApplicationListener<指定事件类型>方式,所有事件来源都支持
  • 只有实际类型为SmartApplicationListener,才会去匹配事件来源

在这里插入图片描述

2.2、实现GenericApplicationListener

  如果监听器是实现GenericApplicationListener方法,那么匹配事件类型和来源都在监听器内部实现,如下以LoggingApplicationListener例如下:

在这里插入图片描述

3、匹配到的监听器

在这里插入图片描述

  LoggingApplicationListener用于初始化日志系统,根据application.properties或环境配置设置日志级别和格式(Spring Boot 的日志系统初始化通常是由该监听器负责)。具体内容后续文章单独讲。

  至于BackgroundPreinitializer和DelegatingApplicationListener虽然匹配上了,但是这个启动开始阶段什么都没有做,后续其他阶段有操作时候再说。

总结

  1. 入口分析
    • 介绍了SpringApplication类中的run方法,其中应用启动监听器获取启动是运行过程中的第一个重要步骤
    • 获取的应用启动监听器为SpringApplicationRunListener,该接口可以在应用启动的各个阶段执行自定义逻辑
  2. SpringApplicationRunListener详细分析
    • SpringApplicationRunListener接口允许监听启动的多个阶段,包括startingenvironmentPreparedcontextPrepared
    • 举例说明了如何实现一个自定义的SpringApplicationRunListener,并在META-INF/spring.factories中进行注册以加入启动流程
  3. 事件和广播器分析
    • 分析了Spring事件机制中SpringApplicationEvent系列事件类型及其触发时机,分别对应应用的不同启动阶段
    • 介绍了ApplicationEventMulticaster广播器的结构和工作原理。它通过SimpleApplicationEventMulticaster将事件广播给符合条件的监听器,支持同步和异步广播
  4. 启动方法解析
    • 通过EventPublishingRunListener来广播事件,其中SimpleApplicationEventMulticaster实现了事件的广播
    • 详细介绍了事件与监听器的匹配规则,监听器可通过实现ApplicationListener<指定事件类型>GenericApplicationListener来适配特定事件类型,监听器匹配时会检查事件类型和来源类型的支持性
    • 总结一句话:启动方法就是匹配监听器并执行
  5. 匹配到的监听器
    • 启动开始阶段中匹配到的监听器做了分类说明,如LoggingApplicationListener负责初始化日志系统,其余监听器在此阶段不执行操作

相关文章:

SpringBoot源码解析(三):启动开始阶段

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 目录 前言一、入口二、SpringApplicationRunListener1、作用…...

C# const与readonly关键字的区别

在C#中&#xff0c;readonly关键字用于定义在对象创建后不能更改的字段。它可以与常量(const)有些相似&#xff0c;但也有显著不同。以下是readonly关键字的一些关键点&#xff1a; 定义与用法&#xff1a; readonly字段可以在类的构造函数中初始化&#xff0c;而const字段必须…...

【数据分享】1901-2023年我国省市县镇四级的逐年降水数据(免费获取/Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月降水栅格数据和Shp和Excel格式的省市县四级逐月降水数据&#xff0c;原始的逐月降水栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据&#xff01;基于逐月数据我们采用求年累计值的方法得到逐年降水栅格数据&#…...

hhdb数据库介绍(9-4)

访问安全 权限体系 计算节点有两类用户&#xff0c;一类是计算节点数据库用户&#xff0c;用于操作数据&#xff0c;执行SELECT&#xff0c;UPDATE&#xff0c;DELETE&#xff0c;INSERT等SQL语句。另一类是关系集群数据库可视化管理平台用户&#xff0c;用于管理配置信息。此…...

苍穹外卖的分层所用到的技术以及工具+jwt令牌流程图(jwt验证)

分层用到的技术以及工具: jwt令牌流程图:...

Python——数列1/2,2/3,3/4,···,n/(n+1)···的一般项为Xn=n/(n+1),当n—>∞时,判断数列{Xn}是否收敛

没注释的源代码 from sympy import * n symbols(n) s n/(n1) print(数列的极限为&#xff1a;,limit(s,n,oo))...

css:还是语法

emmet的使用 emmet是一个插件&#xff0c;Emmet 是 Zen Coding 的升级版&#xff0c;由 Zen Coding 的原作者进行开发&#xff0c;可以快速的编写 HTML、CSS 以及实现其他的功能。很多文本编辑器都支持&#xff0c;我们只是学会使用它&#xff1a; 生成html结构 <!-- emme…...

关于 el-table 的合计行问题

目录 一.自定义合计行 二.合计行不展示&#xff0c;只有缩放/变大窗口或者F12弹出后台时才展示 三.合计行出现了表格滚动条下方 四.合计行整体样式的修改 五.合计行单元格样式修改 1.css 2.jsx方式 六.合计行单元格合并 一.自定义合计行 通过 show-summary 属性开启合计…...

解决SVN更新,提交错误乱码

执行清理操作&#xff0c;没有菜单的情况 1.点击TortoiseSVN-设置-如图勾选 注意&#xff1a;下图没有点击上下文菜单勾选清理 选择对应文件目录&#xff0c;执行【清理】操作 2.如果还是乱码&#xff0c;如上操作勾选解除文件锁定&#xff0c; 执行【破除锁定】后再次执行【…...

《Python网络安全项目实战》项目4 编写网络扫描程序

《Python网络安全项目实战》项目4 编写网络扫描程序 项目4 编写网络扫描程序任务4.1 扫描内网有效IP地址任务描述任务分析任务实施任务拓展 任务4.2 编写端口扫描工具任务描述任务分析任务实施相关知识任务评价任务拓展项目评价 项目4 编写网络扫描程序 许多扫描工具是由Pytho…...

Python金融大数据分析概述

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【海拥导航】&#x1f485; 想寻找共同学习交流&#xff0c;摸鱼划水的小伙伴&#xff0c;请点击【全栈技术交流群】 金融大数据分析在金融科技领域越来越重要&#xff0c;它涉及从海量数据中提取洞察&#xff0c;为金…...

黑马产品经理

1、合格的产品经理 什么是产品&#xff1f; 什么是产品经理&#xff1f; 想清楚产品怎么做的人。 合格的产品经理 2、产品经理的分类 为什么会有不同的分类&#xff1f; 按服务对象划分 按产品平台划分 公司所属行业不同&#xff08;不限于以下&#xff09; 工作内容划分 …...

机器学习——损失函数、代价函数、KL散度

&#x1f33a;历史文章列表&#x1f33a; 机器学习——损失函数、代价函数、KL散度机器学习——特征工程、正则化、强化学习机器学习——常见算法汇总机器学习——感知机、MLP、SVM机器学习——KNN机器学习——贝叶斯机器学习——决策树机器学习——随机森林、Bagging、Boostin…...

首次超越扩散模型和非自回归Transformer模型!字节开源RAR:自回归生成最新SOTA!

文章链接&#xff1a;https://arxiv.org/pdf/2411.00776 项目链接&#xff1a;https://yucornetto.github.io/projects/rar.html 代码&模型链接&#xff1a;https://github.com/bytedance/1d-tokenizer 亮点直击 RAR&#xff08;随机排列自回归训练策略&#xff09;&#x…...

C语言最简单的扫雷实现(解析加原码)

头文件 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2 #include <stdio.h> #include <stdlib.h> #include <time.h> #define numlei 10do while可以循环玩 两个板子&#xff0c;内板子放0&#xff0c;外板子放* set函数初始化两个板子 …...

20. 类模板

一、什么是类模板 类模板用于建立一个通用类&#xff0c;类中的成员数据类型可以不具体指定&#xff0c;用一个虚拟的类型来代替。它的语法格式如下&#xff1a; template<typename T>类模板与函数模板相比主要有两点区别&#xff1a;1) 类模板没有自动类型推导的方式。…...

SSL证书以及实现HTTP反向代理

注意&#xff1a; 本文内容于 2024-11-09 19:20:07 创建&#xff0c;可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容&#xff0c;请访问原文地址&#xff1a;SSL证书以及实现HTTP反向代理。感谢您的关注与支持&#xff01; 之前写的HTTP反向代理工具&…...

多种算法解决组合优化问题平台

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;编程探索专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月11日7点12分 点击开启你的论文编程之旅https://www.aspiringcode.com/content?id17302099790265&uidef7618fa204346ff9…...

【笔记】LLC电路工作频点选择 2-1 输出稳定性的限制

LLC工作模式的分析参考了&#xff1a;现代电力电子学&#xff0c;电力出版社&#xff0c;李永东 1.LLC电路可以选择VCS也可以选择ZVS 1.1选择ZCS时&#xff0c;开关管与谐振电感串联后&#xff0c;与谐振电容并联&#xff1a; 1.2选择ZVS时&#xff0c;开关管仅仅安装在谐振电…...

Linux系统程序设计--2. 文件I/O

文件I/O 标准C的I/O FILE结构体 下面只列出了5个成员 可以观察到&#xff0c;有些函数没有FILE类型的结构体指针例如printf主要是一些标准输出&#xff0c;因为其内部用到了stdin&#xff0c;stdout&#xff0c;stderr查找文件所在的位置:find \ -name stat.h查找头文件所…...

右值引用——C++11新特性(一)

目录 一、右值引用与移动语义 1.左值引用与右值引用 2.移动构造和移动赋值 二、引用折叠 三、完美转发 一、右值引用与移动语义 1.左值引用与右值引用 左值&#xff1a;可以取到地址的值&#xff0c;比如一些变量名&#xff0c;指针等。右值&#xff1a;不能取到地址的值…...

JavaScript 观察者设计模式

观察者模式:观察者模式&#xff08;Observer mode&#xff09;指的是函数自动观察数据对象&#xff0c;一旦对象有变化&#xff0c;函数就会自动执行。而js中最常见的观察者模式就是事件触发机制。 ES5/ES6实现观察者模式(自定义事件) - 简书 先搭架子 要有一个对象&#xff…...

鸿蒙进阶篇-网格布局 Grid/GridItem(二)

hello大家好&#xff0c;这里是鸿蒙开天组&#xff0c;今天让我们来继续学习鸿蒙进阶篇-网格布局 Grid/GridItem&#xff0c;上一篇博文我们已经学习了固定行列、合并行列和设置滚动&#xff0c;这一篇我们将继续学习Grid的用法&#xff0c;实现翻页滚动、自定义滚动条样式&…...

数据仓库之 Atlas 血缘分析:揭示数据流奥秘

Atlas血缘分析在数据仓库中的实战案例 在数据仓库领域&#xff0c;数据血缘分析是一个重要的环节。血缘分析通过确定数据源之间的关系&#xff0c;以及数据在处理过程中的变化&#xff0c;帮助我们更好地理解数据生成的过程&#xff0c;提高数据的可靠性和准确性。在这篇文章中…...

AndroidStudio-滚动视图ScrollView

滚动视图 滚动视图有两种: 1.ScrollView&#xff0c;它是垂直方向的滚动视图;垂直方向滚动时&#xff0c;layout_width属性值设置为match_parent&#xff0c;layout_height属性值设置为wrap_content。 例如&#xff1a; &#xff08;1&#xff09;XML文件中: <?xml ve…...

嵌入式硬件实战基础篇(一)-STM32+DAC0832 可调信号发生器-产生方波-三角波-正弦波

引言&#xff1a;本内容主要用作于学习巩固嵌入式硬件内容知识&#xff0c;用于想提升下述能力&#xff0c;针对学习STM32与DAC0832产生波形以及波形转换&#xff0c;对于硬件的降压和对于前面硬件篇的实际运用&#xff0c;针对仿真的使用&#xff0c;具体如下&#xff1a; 设…...

ElasticSearch的Python Client测试

一、Python环境准备 1、下载Python安装包并安装 https://www.python.org/ftp/python/3.13.0/python-3.13.0-amd64.exe 2、安装 SDK 参考ES官方文档: https://www.elastic.co/guide/en/elasticsearch/client/index.html python -m pip install elasticsearch一、Client 代…...

【eNSP】企业网络架构链路聚合、数据抓包、远程连接访问实验(二)

一、实验目的 网络分段与VLAN划分&#xff1a; 通过实验了解如何将一个大网络划分为多个小的子网&#xff08;VLAN&#xff09;&#xff0c;以提高网络性能和安全性。 VLAN间路由&#xff1a; 学习如何配置VLAN间的路由&#xff0c;使不同VLAN之间能够通信。 网络设备配置&am…...

独立站 API 接口的性能优化策略

一、缓存策略* 数据缓存机制 内存缓存&#xff1a;利用内存缓存系统&#xff08;如 Redis 或 Memcached&#xff09;来存储频繁访问的数据。例如&#xff0c;对于商品信息 API&#xff0c;如果某些热门商品的详情&#xff08;如价格、库存、基本描述等&#xff09;被大量请求…...

不一样的CSS(一)

目录 前言&#xff1a; 一、规则图形 1.介绍&#xff1a; 2.正方形与长方形&#xff08;实心与空心&#xff09; 2.1正方形&#xff1a; 2.2长方形 3.圆形与椭圆形&#xff08;空心与实心&#xff09; 3.1圆形与椭圆形 4.不同方向的三角形 4.1原理 4.2边框属性 5.四…...

网站备案那个省份/国际新闻最新消息十条摘抄

2019独角兽企业重金招聘Python工程师标准>>> 使用logback,忽然想到这个问题&#xff0c;然后问了几个同事都没研究过&#xff0c;我来看看logback是如何知道的 打断点如下&#xff1a; stop in com.sql.mysql.sharding.plugin.ExecutorInterceptor.interceptstop in…...

浦东新区中国建设银行官网站/文军seo

今天对数据库做异机恢复&#xff0c;我们知道在恢复过程中是不对临时表空间进行恢复的&#xff0c;所以在进行resetlogs打开数据库之后&#xff0c;需要对临时表空间进行处理&#xff0c;处理方式如下&#xff1a; 1 添加新的临时表空间文件。 2 删除旧的临时表空间文件。 al…...

郑州建设网站/易搜搜索引擎

百度百科&#xff1a; 哈夫曼树也可以是k叉的&#xff0c;只是在构造k叉哈夫曼树时需要先进行一些调整。构造哈夫曼树的思想是每次选k个权重最小的元素来合成一个新的元素&#xff0c;该元素权重为k个元素权重之和。但是当k大于2时&#xff0c;按照这个步骤做下去可能到最后剩…...

邯郸网站建设优化/怎么在百度上打广告

人类重新发现世界的速度常常超出自身的想象。从理论设想到科学验证&#xff0c;再到技术应用&#xff0c;这3个阶段的间隔正在被缩短&#xff0c;甚至合而为一。比特币和区块链概念的出现正是对这一判断的完美阐释。 区块链技术是一种基于大数据共享理论而存在的基础技术&#…...

枣庄手机网站制作/网络营销专业毕业论文

类型1:内连接&#xff08;两张表相同数据都显示&#xff09; SELECT A.PK AS A_PK,A.Value AS A_Value,B.PK AS B_PK,B.Value AS B_ValueFROM table_a A INNER JOIN table_b B ON A.PK B.PK; 注意事项&#xff1a; 1、on 表示连接条件&#xff1b;条件字段代表相同的业务含义…...

做相片软件网站/海外网站

1&#xff09;IBM 咨询部 高级顾问 22K上下。要求高啊&#xff01;实际上工作难度并不大&#xff0c;就是门口太高&#xff01;BS。 2&#xff09;HP 服务器售前 11K-20K不等。感觉HP不是IT公司了。 3&#xff09; CISCO售前 18K-25K 不等。你现在进去肯定拿不到这个工资。除非…...