SpringBoot系統學習 - 啟動篇

藍星花發表於2018-09-02

1.前言

到這裡,我們對springBoot對常用的工具整合都有一定的瞭解了,那我們是否想過:springboot啟動的過程都幹了些啥事情啊? 好好想一想?Bean注入容器,配置注入…?


2.啟動流程示意圖

SpringBoot將spring應用的啟動流程進行了一個“模板化”的操作,所以我們才能通過SpringApplication.run(XXX.class, args)的方式來進行一站式的啟動。其內部邏輯也是個較複雜的過程,下文將對執行流程進行闡述。本流程參考的SpringBoot版本為1.4.3.RELEASE。
這裡寫圖片描述

先有個印象就好。


1)SpringApplicationRunListener
SpringApplicationRunListener是SpringBoot執行過程中,不同執行時間點時間通知的監聽者,一般來說也沒有必要自己實現一個SpringApplicationRunListener,即使是SpringBoot預設也只實現了一個org.springframework.boot.context.event.Event
PublishingRunListener。通過這個類,在SpringBoot啟動時,在不同的時間點發布不同的應用事件型別ApplicationEvent。

SpringBoot初始化時載入的ApplicationListener如果對這些事件感興趣,則可以接收並處理。

public interface SpringApplicationRunListener {
    void started();
    void environmentPrepared(ConfigurableEnvironment environment);
    void contextPrepared(ConfigurableApplicationContext context);
    void contextLoaded(ConfigurableApplicationContext context);
    void finished(ConfigurableApplicationContext context, Throwable exception);
}

2) ApplicationContextInitializer
通過這個類,可以在ApplicationContext呼叫refresh()方法前,對ApplicationContext物件做進一步的設定或者處理。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

3) ApplicationRunner和CommandLineRunner
需要在容器啟動的時候執行一些內容。比如讀取配置檔案,資料庫連線之類的。SpringBoot給我們提供了兩個介面來幫助我們實現這種需求。這兩個介面分別為CommandLineRunner和ApplicationRunner。他們的執行時機為容器啟動完成的時候。

public interface ApplicationRunner {
    void run(ApplicationArguments args) throws Exception;
}

public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

3.執行流程

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

new SpringApplication(primarySources)幹了那些事情呢?
這裡寫圖片描述
共幹了4件事:

1.推斷應用型別是Standard還是Web
這裡寫圖片描述

private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null)) {
            return WebApplicationType.REACTIVE;
        } else {
            String[] var1 = WEB_ENVIRONMENT_CLASSES;
            int var2 = var1.length;
            for(int var3 = 0; var3 < var2; ++var3) {
                String className = var1[var3];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return WebApplicationType.NONE;
                }
            }
            return WebApplicationType.SERVLET;
        }
    }

可能會出現三種結果:
這裡寫圖片描述

1) WebApplicationType.REACTIVE - 當類路徑中存在REACTIVE_WEB_ENVIRONMENT_CLASS並且不存在MVC_
WEB_ENVIRONMENT_CLASS時
2) WebApplicationType.NONE - 也就是非Web型應用(Standard型),此時類路徑中不包含WEB_ENVIRONMENT_CLASSES中定義的任何一個類時
3) WebApplicationType.SERVLET - 類路徑中包含了WEB_ENVIRONMENT_CLASSES中定義的所有型別時


2.設定初始化器(Initializer)
這裡寫圖片描述

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

// 這裡的入參type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 使用Set儲存names來避免重複元素
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 根據names來進行例項化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    // 對例項進行排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

這裡面首先會根據入參type讀取所有的names(是一個String集合),然後根據這個集合來完成對應的例項化操作:
這裡寫圖片描述
從類路徑的META-INF/spring.factories處讀取相應配置檔案,然後進行遍歷,讀取配置檔案中Key為:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包為例,它的META-INF/spring.factories部分定義如下所示:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

初始化步驟很直觀,沒什麼好說的,類載入,確認被載入的類確實是org.springframework.context.ApplicationContextInitializer的子類,然後就是得到構造器進行初始化,最後放入到例項列表中。

因此,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個介面是這樣定義的:
這裡寫圖片描述
根據類文件,這個介面的主要功能是:
在Spring上下文被重新整理之前進行初始化的操作。典型地比如在Web應用中,註冊Property Sources或者是啟用Profiles。Property Sources比較好理解,就是配置檔案。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),載入不同的配置項而抽象出來的一個實體。


3.設定監聽器(Listener)
這裡寫圖片描述

// 這裡的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

可以發現,這個載入相應的類名,然後完成例項化的過程和上面在設定初始化器時如出一轍,同樣,還是以spring-boot-autoconfigure這個包中的spring.factories為例,看看相應的Key-Value:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

ApplicationListener介面,它是Spring框架中一個相當基礎的介面了,程式碼如下:
這裡寫圖片描述
這個介面基於JDK中的EventListener介面,實現了觀察者模式。對於Spring框架的觀察者模式實現,它限定感興趣的事件型別需要是ApplicationEvent型別的子類,而這個類同樣是繼承自JDK中的EventObject類。


4) 推斷應用入口類
這裡寫圖片描述

這裡寫圖片描述

至此,對於SpringApplication例項的初始化過程就結束了。

構造完了,我們執行run方法咯
這裡寫圖片描述
SpringApplication.run方法

// 執行run方法
public ConfigurableApplicationContext run(String... args) {
  // 計時工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    // 設定java.awt.headless系統屬性為true - 沒有圖形化介面
    configureHeadlessProperty();

    // KEY 1 - 獲取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 發出開始執行的事件
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);

        // KEY 2 - 根據SpringApplicationRunListeners以及引數來準備環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 準備Banner列印器 - 就是啟動Spring Boot的時候列印在console上的ASCII藝術字型
        Banner printedBanner = printBanner(environment);

        // KEY 3 - 建立Spring上下文
        context = createApplicationContext();

        // 準備異常報告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // KEY 4 - Spring上下文前置處理
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);

        // KEY 5 - Spring上下文重新整理
        refreshContext(context);

        // KEY 6 - Spring上下文後置處理
        afterRefresh(context, applicationArguments);

        // 發出結束執行的事件
        listeners.finished(context, null);

        // 停止計時器
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}

這個run方法包含的內容也是有點多的,根據上面列舉出的關鍵步驟逐個進行分析:

1) 第一步 - 獲取所謂的run listeners:
2)第二步 - 根據SpringApplicationRunListeners以及引數來準備環境
3)第三步 - 建立Spring上下文
4) 第四步 - Spring上下文前置處理
5) 第五步 - Spring上下文重新整理
6) 第六步 - Spring上下文後置處理

1) 第一步 - 獲取所謂的run listeners:
這裡仍然利用了getSpringFactoriesInstances方法來獲取例項:所以這裡還是故技重施,從META-INF/spring.factories中讀取Key為org.springframework.boot.SpringApplicationRunListener的Values:
比如在spring-boot包中的定義的spring.factories:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

2)第二步 - 根據SpringApplicationRunListeners以及引數來準備環境
對於Web應用而言,得到的environment變數是一個StandardServletEnvironment的例項。得到例項後,會呼叫前面RunListeners中的environmentPrepared方法:

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
            this.application, this.args, environment));
}

在這裡,定義的廣播器就派上用場了,它會釋出一個ApplicationEnvironmentPreparedEvent事件。

那麼有釋出就有監聽,在構建SpringApplication例項的時候不是初始化過一些ApplicationListeners嘛,其中的Listener就可能會監聽ApplicationEnvironmentPreparedEvent事件,然後進行相應處理。

**3) 第三步 - 建立Spring上下文
對於我們的Web應用,上下文型別就是DEFAULT_WEB_CONTEXT_CLASS。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

// WEB應用的上下文型別
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

4) 第四步 - Spring上下文前置處理
配置Bean生成器以及資源載入器(如果它們非空)
呼叫初始化器:建立SpringApplication例項時設定的初始化器了,依次對它們進行遍歷,並呼叫initialize方法。

5) 第五步 - Spring上下文重新整理
註冊關閉容器時的鉤子函式的預設實現是在AbstractApplicationContext類中:
如果沒有提供自定義的shutdownHook,那麼會生成一個預設的,並新增到Runtime中。預設行為就是呼叫它的doClose方法,完成一些容器銷燬時的清理工作。

6) 第六步 - Spring上下文後置處理
所謂的後置操作,就是在容器完成重新整理後,依次呼叫註冊的Runners。Runners可以是兩個介面的實現類:

org.springframework.boot.ApplicationRunner
org.springframework.boot.CommandLineRunner

至此,SpringApplication的run方法就分析完畢了。


總結

本文分析了Spring Boot啟動時的關鍵步驟,主要包含以下兩個方面:
SpringApplication例項的構建過程
其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都通過META-INF/spring.factories完成定義。

SpringApplication例項run方法的執行過程
其中主要有一個SpringApplicationRunListeners的概念,它作為Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication例項的構建過程中得到的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴充套件性。

如果從可擴充套件性的角度出發,應用開發者可以在Spring Boot容器的啟動階段,擴充套件哪些內容呢:

初始化器(Initializer)
監聽器(Listener)
容器重新整理後置Runners(ApplicationRunner或者CommandLineRunner介面的實現類)
啟動期間在Console列印Banner的具體實現類

相關文章