springboot啓動流程(一)構造SpringApplication實例對象

全部文章

http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml

 

啓動入口

本文是springboot啓動流程的第一篇,涉及的內容是SpringApplication這個對象的實例化過程。爲何從SpringApplication這個對象提及呢?咱們先看一段很熟悉的代碼片斷react

@SpringBootApplication
public class SpringBootLearnApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLearnApplication.class, args);
    }

}

springboot項目從一個main方法開始,main方法將會調用SpringApplication的run方法開始springboot的啓動流程。因此,本文即從構造SpringApplication對象開始。web

 

咱們跟進SpringApplication的run方法spring

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

這是一個靜態方法,入參有兩個:springboot

1)main方法所在的類,該類後續將被做爲主要的資源來使用,好比經過該類獲取到basePackage;函數

2)main方法的命令行參數,命令行參數能夠經過main傳入,也就意味着能夠在springboot啓動的時候設置對應的參數,好比當前是dev環境、仍是production環境等。this

第2行代碼,run方法將調用另一個內部run方法,並返回一個ConfigurableApplicationContext,預示着spring容器將在後續過程當中建立。spa

 

跟進另外一個run方法命令行

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

run方法中先是構造了一個SpringApplication實例對象,然後調用了SpringApplication的成員方法run,這個run方法將包含springboot啓動流程的核心邏輯。本文只討論SpringApplication的實例化過程。code

 

構造函數

跟進SpringApplication的構造函數中

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

構造函數調用了另一個構造函數,繼續跟進

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
     // 設置資源加載器
     this.resourceLoader = resourceLoader; 
     // 設置主要資源類
     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
     // 推斷當前應用的類型
     this.webApplicationType = WebApplicationType.deduceFromClasspath();
 
     // 設置ApplicationContext的初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 設置Application監聽器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 推斷並設置主類
    this.mainApplicationClass = deduceMainApplicationClass();
}

構造過程主要包含:

1)推斷當前應用類型

2)設置ApplicationContext初始化器、Application監聽器

3)根據堆棧來推斷當前main方法所在的主類

 

推斷當前應用類型

WebApplicationType是一個枚舉對象,枚舉了可能的應用類型

public enum WebApplicationType {
 
     /**
      * 非web應用類型,不啓動web容器
      */
     NONE,
     /**
      * 基於Servlet的web應用,將啓動Servlet的嵌入式web容器
      */
    SERVLET,
    /**
     * 基於reactive的web應用,將啓動reactive的嵌入式web容器
     */
    REACTIVE;

    // 省略...
}

deduceFromClasspath方法將會推斷出當前應用屬於以上三個枚舉實例的哪個,跟進方法

static WebApplicationType deduceFromClasspath() {
     // 類路徑中是否包含DispatcherHandler,且不包含DispatcherServlet,也不包含ServletContainer,那麼是reactive應用
     if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
             && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
         return WebApplicationType.REACTIVE;
     }
     // 若是Servlet或者ConfigurableWebApplicationContext不存在,那麼就是非web應用
     for (String className : SERVLET_INDICATOR_CLASSES) {
         if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    // 不然都是Servlet的web應用
    return WebApplicationType.SERVLET;
}

推斷過程將根據類路徑中是否有指示性的類來判斷

 

設置ApplicationContext初始化器、Application監聽器

getSpringFactoriesInstances(ApplicationContextInitializer.class)

這個方法調用將會從META-INF/spring.factories配置文件中找到全部ApplicationContextInitializer接口對應的實現類配置,而後經過反射機制構造出對應的實例對象。

getSpringFactoriesInstances(ApplicationListener.class)

這個方法也是同樣的作法,將會獲取ApplicationListener接口的全部配置實例對象

有關於如何從spring.factories配置文件中獲取配置並構造出實例對象請看:spring.factories配置文件的工廠模式

 

根據堆棧來推斷當前main方法所在的主類

構造SpringApplication還有最後一步,推斷出main方法所在的主類。咱們跟進deduceMainApplicationClass方法

private Class<?> deduceMainApplicationClass() {
     try {
         // 獲取堆棧鏈路
         StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
         // 遍歷每個棧幀信息
         for (StackTraceElement stackTraceElement : stackTrace) {
             // 若是該棧幀對應的方法名等於main
             if ("main".equals(stackTraceElement.getMethodName())) {
                 // 獲取該類的class對象
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

該方法採用遍歷棧幀的方式來判斷最終main方法落在哪一個棧幀上,並經過forName來獲取該類

 

總結

到這裏本文就結束了,核心點就在於SpringApplication的實例化,能夠看出最主要的就是作了應用類型的推斷,後面的Application建立、Environment建立也會基於該類型。

相關文章
相關標籤/搜索