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


所有文章

https://www.cnblogs.com/lay2017/p/11478237.html

 

啟動入口

本文是springboot啟動流程的第一篇,涉及的內容是SpringApplication這個對象的實例化過程。為什么從SpringApplication這個對象說起呢?我們先看一段很熟悉的代碼片段

@SpringBootApplication
public class SpringBootLearnApplication {

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

}

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

 

我們跟進SpringApplication的run方法

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

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

1)main方法所在的類,該類后續將被作為主要的資源來使用,比如通過該類獲取到basePackage;

2)main方法的命令行參數,命令行參數可以通過main傳入,也就意味着可以在springboot啟動的時候設置對應的參數,比如當前是dev環境、還是production環境等。

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

 

跟進另一個run方法

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

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

 

構造函數

跟進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創建也會基於該類型。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM