掘金 后端 ( ) • 2024-04-13 11:18

highlight: atelier-cave-dark
theme: channing-cyan

前言

本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。

之前分析到Spring Boot项目的run方法中的创建监听器SpringApplicationRunListeners实例的方法。下面接着阅读run方法的代码。

run

public ConfigurableApplicationContext run(String... args) {
// 为了计时用的,老版本和新版本不一样
   long startTime = System.nanoTime();
   // 初始化一个引导器的上下文,这是属于Spring Boot的上下文。后边还有一个Spring的上下文。apach好喜欢context这个东西,证明写框架这个context是真的好用。
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   // 这是Spring的上下文,在这里定义,在下面进行的初始化
   ConfigurableApplicationContext context = null;
   // 配置一个系统属性
   configureHeadlessProperty();
   // 获取配置文件的监听器 重点 也是扩展点,凡是读取配置文件的地方都是扩展点,因为配置在配置文件中的initializer、listener都会在某个阶段被调用
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
      listeners.started(context, timeTakenToStartup);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }
   try {
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      listeners.ready(context, timeTakenToReady);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

listeners.starting

这段代码很值得分析,因为在Spring启动的过程中会有大量用到这个代码的地方,让阅读源码更流畅。对于个人来说也是学习优秀代码的写法的一个好的场景。

下面这个方法是SpringApplicationRunListeners的。

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
   doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
         (step) -> {
            if (mainApplicationClass != null) {
               step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
         });
}

从这里可以看出来,如果你要对一个类进行不同的使用的时候就给他包装异常,不要让他分散。像不像我们用Spring MVC写业务代码的时候的思想。就是我们的controller接收到接口请求的时候大多数是要处理数据库的,但是我们不直接用mapper,而是用一个service包装一下,通过service操作mapper。这里的SpringApplicationRunListeners就像是MVC思想中的service。而它其中的属性listenersapplicationStartup是不是就像mapper呢。

这个我个人感觉还是蛮喜欢的,有机会一定用一下。

image.png

它本身的每个方法都调用到了doWithListeners方法。

private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
      Consumer<StartupStep> stepAction) {
   StartupStep step = this.applicationStartup.start(stepName);
   // 遍历构造函数初始化的Listener.
   // 如果我们没有扩展的话就只有自带的EventPublishingRunListener
   // 执行每个不同场景下定义监听器的行为
   this.listeners.forEach(listenerAction);
   if (stepAction != null) {
   // 这个目前感觉不是重点
      stepAction.accept(step);
   }
   step.end();
}

这些就是构造函数中初始化的属性。

image.png

方法参数的函数都是构造函数的属性类型

image.png

this.listeners.forEach(listenerAction)

这里的listenerAction是在其他方法中定义的。我们看starting吧。

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
   doWithListeners("spring.boot.application.starting", 
   // 行为就是调用listener的starting方法。这个listener默认是EventPublishingRunListener。如果我们要扩展要有相同的方法名哟。这种肯定是通过接口定义了一个共同的方法,实现接口就好
   (listener) -> listener.starting(bootstrapContext),
         (step) -> {
            if (mainApplicationClass != null) {
               step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
         });
}

starting

EventPublishingRunListener的starting方法

public void starting(ConfigurableBootstrapContext bootstrapContext) {
   this.initialMulticaster
         .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}

调用的是我们的构造函数初始化的属性SimpleApplicationEventMulticaster的方法。所以行为最终是靠它来决定的。

image.png

multicastEvent

image.png

public 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) {
      // 这是异步调用 但是这个默认是null
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
      // 调用符合监听事件类型的listener
         invokeListener(listener, event);
      }
   }
}

resolveDefaultEventType

该方法的作用是解析默认事件类型。它通过调用ResolvableType.forClass()方法,传入event.getClass()的返回值,来获取事件的ResolvableType对象,并将其作为方法的返回值。

这个方法的主要作用是在于获取事件的泛型类型信息,以便于后续对事件进行更细致的处理或分发。

主要是解析出这两个属性。

image.png

getApplicationListeners

该函数 getApplicationListeners 的主要功能是从Spring框架的上下文中为指定的 ApplicationEvent 事件查找并返回相关的 ApplicationListener 监听器集合。为了提高性能,该函数采用缓存机制来避免重复查找。以下是该函数的具体执行步骤:

1、创建缓存键:

  • 函数首先获取事件源(event.getSource()),并检查其是否非空。若非空,获取事件源的类(source.getClass());否则,将事件源类设为 null。

  • 使用事件类型(eventType)和事件源类构建一个 ListenerCacheKey 对象,作为查找缓存的键。

2、检查缓存:

  • 使用创建的 ListenerCacheKey 在 retrieverCache 缓存中查找是否存在已缓存的 CachedListenerRetriever 对象。

  • 若找到(即 existingRetriever 不为 null),表示有对应的监听器集合已经缓存,后续将直接从缓存中获取。

3、缓存缺失处理:

  • 若缓存中未找到对应 CachedListenerRetriever,函数将进一步判断是否可以安全地为当前事件和事件源类型创建新的缓存条目:
    • 检查当前的 beanClassLoader 是否为空,以及事件类和事件源类(若非空)是否在 beanClassLoader 中是可缓存安全的(使用 ClassUtils.isCacheSafe 方法)。
  • 若满足上述条件,创建一个新的 CachedListenerRetriever 对象,并尝试使用 putIfAbsent 方法将其添加到缓存中。此操作是线程安全的,仅当缓存中尚无对应键值时才会插入新对象。同时,检查是否已有其他线程在此期间完成了相同键值的插入。
    • 若插入成功,设置 newRetriever 为 null,因为另一个线程已填充了新缓存条目的数据,当前线程无需再进行填充。
    • 若插入失败(即已有其他线程插入),保留 newRetriever 以供后续填充数据。

4、从缓存或新检索器获取监听器集合:

  • 若存在已缓存的 existingRetriever,尝试从中获取应用程序监听器集合。如果集合不为空,直接返回该集合;若集合为空,说明另一个线程正在填充数据,此时按照未找到缓存条目的情况处理。

  • 若 existingRetriever 为空或其集合为空,且之前创建了新的 CachedListenerRetriever(即 newRetriever 非空),则调用 retrieveApplicationListeners 方法,传入 eventType、sourceType 和 newRetriever,该方法将实际查找并填充监听器集合。返回该方法返回的结果。

综上所述,该函数通过缓存机制有效地从Spring上下文中查找与特定 ApplicationEvent 关联的 ApplicationListener 集合,确保在多线程环境中高效、安全地访问和更新缓存数据。在缓存未命中或需要更新的情况下,会调用 retrieveApplicationListeners 进行实际的监听器查找工作

protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {

   Object source = event.getSource();
   Class<?> sourceType = (source != null ? source.getClass() : null);
   ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

   // Potential new retriever to populate
   CachedListenerRetriever newRetriever = null;

   // Quick check for existing entry on ConcurrentHashMap
   CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
   if (existingRetriever == null) {
      // Caching a new ListenerRetriever if possible
      if (this.beanClassLoader == null ||
            (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                  (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
         newRetriever = new CachedListenerRetriever();
         existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
         if (existingRetriever != null) {
            newRetriever = null;  // no need to populate it in retrieveApplicationListeners
         }
      }
   }

   if (existingRetriever != null) {
      Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
      if (result != null) {
         return result;
      }
      // If result is null, the existing retriever is not fully populated yet by another thread.
      // Proceed like caching wasn't possible for this current local attempt.
   }

   return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

OK 今天先到这里吧。

See you next time :)