spring-boot-2.0.3启动源码篇二 - run方法(一)之SpringApplicationRunListener

前言

  Springboot 启动源码系列还只写了一篇,已经过去一周,又到了每周一更的时间了(是不是很熟悉?),大家有没有很期待了?我会尽量保证启动源码系列每周一更,争取不让大家每周的期望落空。一周之中可能会插入其他内容的博文,可能和 springboot 启动源码有关,也可能和启动源码无关。

前情回顾

  这篇是在设计模式之观察者模式,事件机制的底层原理spring-boot-2.0.3 启动源码篇一 - SpringApplication 构造方法这两篇的基础上进行的,没看的小伙伴可以先去看看这两篇。如果大家不想去看,这里我帮大家简单回顾下。

  设计模式之观察者模式,事件机制的底层原理

    SpringApplication 的构造方法主要做了以下 3 件事:

      1、推测 web 应用类型,并赋值到属性 webApplicationType

      2、设置属性 List<ApplicationContextInitializer<?>> initializers 和 List<ApplicationListener<?>> listeners

        中途读取了类路径下所有 META-INF/spring.factories 的属性,并缓存到了 SpringFactoriesLoader 的 cache 缓存中,而这个 cache 会在本文中用到

      3、 推断主类,并赋值到属性 mainApplicationClass

  spring-boot-2.0.3 启动源码篇一 - SpringApplication 构造方法

    事件机制是基于观察者模式实现的。主要包括几下 4 个角色:

      事件源:触发事件的主体

      事件:事件本身,指的是 EventObject 中的 source,具体可以是任何数据(包括事件源),用来传递数据

      事件监听器:当事件发生时,负责对事件的处理

      事件环境:整个事件所处的上下文,对整个事件提供支持

SpringApplicationRunListener

  run 方法的源代码如下

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    // 秒表,用于记录启动时间;记录每个任务的时间,最后会输出每个任务的总费时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // spring 应用上下文,也就是我们所说的 spring 根容器
    ConfigurableApplicationContext context = null;
    // 自定义 SpringApplication 启动错误的回调接口
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置 jdk 系统属性 java.awt.headless,默认情况为 true 即开启;更多 java.awt.headless 信息大家可以去查阅资料,这不是本文重点
    configureHeadlessProperty();
    // 获取启动时监听器
    SpringApplicationRunListeners listeners = getRunListeners(args)
    // 触发启动事件,相应的监听器会被调用,这是本文重点
    listeners.starting(); 
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
    listeners.running(context);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IllegalStateException(ex);
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> context;

}

View Code

  再讲今天的主角之前,我们先来看看 ConfigurableApplicationContext,从名字来看就是:配置应用上下文,会根据 class 路径下的类初始化配置合适的应用上下文,比如是普通的 spring 应用(非 web 应用),还是 web 应用上下文。类图和类继承图如下

  ConfigurableApplicationContext 类图

  ConfigurableApplicationContext 类继承图

  ConfigurableApplicationContext 不会在本文详解,他的创建会在后续的博文中讲到,这里只是一个提醒。今天的主角是以下两行代码

SpringApplicationRunListeners listeners = getRunListeners(args)
listeners.starting();

  我们今天的目的就是看看这两行代码到底做了些什么

getRunListeners

  我们先看看 SpringApplicationRunListeners 和 SpringApplicationRunListener。

    SpringApplicationRunListeners 的类注释很简单:

      一个存 SpringApplicationRunListener 的集合,里面有些方法,后续都会讲到;

    SpringApplicationRunListener 的接口注释也简单:

      监听 SpringApplication 的 run 方法。通过 SpringFactoriesLoader 加载 SpringApplicationRunListener(一个或多个),SpringApplicationRunListener 的实现类必须声明一个接收 SpringApplication 实例和 String[] 数组的公有构造方法。

  接下来我们看看 getRunListeners 方法,源代码如下

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));}

  初略来看的话,就是返回一个新的 SpringApplicationRunListeners 实例对象;

  细看的话,发现有个 getSpringFactoriesInstances 方法的调用,这个方法大家还记得吗?(详情请看spring-boot-2.0.3 启动源码篇一 - SpringApplication 构造方法)getSpringFactoriesInstances 在 SpringApplication 的构造方法中调用了两次,分别用来设置属性 List<ApplicationContextInitializer<?>> initializers 和 List<ApplicationListener<?>> listeners。getSpringFactoriesInstances 在第一次被调用时会将类路径下所有的 META-INF/spring.factories 的文件中的属性进行加载并缓存到 SpringFactoriesLoader 的缓存 cache 中,下次被调用的时候就直接从 SpringFactoriesLoader 的 cache 中取数据了。这次就是从 SpringFactoriesLoader 的 cache 中取 SpringApplicationRunListener 类型的类(全限定名),然后实例化后返回。我们来跟下这次 getSpringFactoriesInstances 获取的的内容

  EventPublishingRunListener 的构造方法中,构造了一个 SimpleApplicationEventMulticaster 对象,并将 SpringApplication 的 listeners 中的全部 listener 赋值到 SimpleApplicationEventMulticaster 对象的属性 defaultRetriever(类型是 ListenerRetriever)的 applicationListeners 集合中,如下图

 

  总的来说,getRunListeners 做了什么事呢?就是获取 SpringApplicationRunListener 类型的实例(EventPublishingRunListener 对象),并封装进 SpringApplicationRunListeners 对象,然后返回这个 SpringApplicationRunListeners 对象。说的再简单点,getRunListeners 就是准备好了运行时监听器 EventPublishingRunListener。

listeners.starting()

  我们看看 starting 方法做了些什么事

  构建了一个 ApplicationStartingEvent 事件,并将其发布出去,其中调用了 resolveDefaultEventType 方法,该方法返回了一个封装了事件的默认类型(ApplicationStartingEvent)的 ResolvableType 对象。我们接着往下看,看看这个发布过程做了些什么

  multicastEvent

    源代码如下

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {executor.execute(() -> invokeListener(listener, event));
        }
        else {invokeListener(listener, event);
        }
    }
}

    初略的看,就是遍历 getApplicationListeners(event, type),然后对每个 listener 进行 invokeListener(listener, event)

  getApplicationListeners

    根据其注释可知,该方法作用:返回与给定事件类型匹配的 ApplicationListeners 集合,非匹配的侦听器会被提前排除;允许根据缓存的匹配结果来返回。

    从上图可知,主要涉及到 3 个点:缓存 retrieverCache、retrieveApplicationListeners 已经 retrieveApplicationListeners 中调用的 supportsEvent 方法。流程是这样的:

      1、缓存中是否有匹配的结果,有则返回

      2、若缓存中没有匹配的结果,则从 this.defaultRetriever.applicationListeners 中过滤,这个 this 表示的 EventPublishingRunListener 对象的属性 initialMulticaster(也就是 SimpleApplicationEventMulticaster 对象,而 defaultRetriever.applicationListeners 的值也是在 EventPublishingRunListener 构造方法中初始化的

      3、过滤过程,遍历 defaultRetriever.applicationListeners 集合,从中找出 ApplicationStartingEvent 匹配的 listener,具体的匹配规则需要看各个 listener 的 supportsEventType 方法(有两个重载的方法)

      4、将过滤的结果缓存到 retrieverCache

      5、将过滤出的结果返回回去

    我们看看,过滤出的 listener 对象有哪些

  invokeListener

    其注释:使用给定的事件调用给定的监听器

    getApplicationListeners 方法过滤出的监听器都会被调用,过滤出来的监听器包括 LoggingApplicationListener、BackgroundPreinitializer、DelegatingApplicationListener、LiquibaseServiceLocatorApplicationListener、EnableEncryptablePropertiesBeanFactoryPostProcessor 五种类型的对象。这五个对象的 onApplicationEvent 都会被调用。

    那么这五个监听器的 onApplicationEvent 都做了些什么了,我这里大概说下,细节的话大家自行去跟源码

      LoggingApplicationListener:检测正在使用的日志系统,默认是 logback,支持 3 种,优先级从高到低:logback > log4j > javalog。此时日志系统还没有初始化

      BackgroundPreinitializer:另起一个线程实例化 Initializer 并调用其 run 方法,包括验证器、消息转换器等等

      DelegatingApplicationListener:此时什么也没做

      LiquibaseServiceLocatorApplicationListener:此时什么也没做

      EnableEncryptablePropertiesBeanFactoryPostProcessor:此时仅仅打印了一句日志,其他什么也没做

总结

  事件机制要素

    这里和大家一起把事件 4 要素找出来

    事件源:SpringApplication

    事件:ApplicationStartingEvent

    监听器:过滤后的监听器,具体 5 个上文中已经说过

    事件环境:EventPublishingListener,提供环境支持事件,并且发布事件(starting 方法)

  监听器数量

    项目中集成的功能的多少的不同,从 spring.factories 加载的属性数量也不同,自然监听器数量也会有所不同;如果大家看源码的时候发现比我的多或者少,不要惊慌,这是很正常的,因为我们集成的功能有所差别。

  友情提醒

    博文中有些地方分析的不是特别细,需要大家自行去跟源码,没涉及到复杂的模式,相信大家也都能看懂。过滤监听器的时候用到了 supportsEvent 方法,这个方法里面涉及到了适配器模式,改天我结合 session 共享给大家分析下适配器模式。

  高光时刻

    有时候,不是对手有多强大,只是我们不敢去尝试;勇敢踏出第一步,你会发现自己比想象中更优秀!诚如海因斯第一次跑进人类 10s 大关时所说:上帝啊,原来那扇门是虚掩着的!

参考

  springboot 源码