天天看點

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  前面給大家介紹了SpringBoot啟動的核心流程,本文開始給大家詳細的來介紹SpringBoot啟動中的具體實作的相關細節。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

SpringApplication構造器

  首先我們來看下在SpringApplication的構造方法中是如何幫我們完成這4個核心操作的。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析
@SuppressWarnings({ "unchecked", "rawtypes" })
  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 傳遞的resourceLoader為null
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 記錄主方法的配置類名稱
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 記錄目前項目的類型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加載配置在spring.factories檔案中的ApplicationContextInitializer對應的類型并執行個體化
    // 并将加載的資料存儲在了 initializers 成員變量中。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 初始化監聽器 并将加載的監聽器執行個體對象存儲在了listeners成員變量中
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 反推main方法所在的Class對象 并記錄在了mainApplicationClass對象中
    this.mainApplicationClass = deduceMainApplicationClass();
  }      

1.webApplicationType

  首先來看下webApplicationType是如何來推導出目前啟動的項目的類型。通過代碼可以看到是通過deduceFromClassPath()方法根據ClassPath來推導出來的。

this.webApplicationType = WebApplicationType.deduceFromClasspath();      

  跟蹤進去看代碼

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
        return WebApplicationType.NONE;
      }
    }
    return WebApplicationType.SERVLET;
  }      

  在看整體的實作邏輯之前,我們先分别看兩個内容,第一就是在上面的代碼中使用到了相關的靜态變量。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  這些靜态變量其實就是一些綁定的Java類的全類路徑。第二個就是 ​

​ClassUtils.isPresent()​

​方法,該方法的邏輯也非常簡單,就是通過反射的方式擷取對應的類型的Class對象,如果存在傳回true,否則傳回false

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  是以到此推導的邏輯就非常清楚了

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

2.setInitializers

  然後我們再來看下如何實作加載初始化器的。

// 加載配置在spring.factories檔案中的ApplicationContextInitializer對應的類型并執行個體化
    // 并将加載的資料存儲在了 initializers 成員變量中。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));      

  首先所有的初始化器都實作了 ​

​ApplicationContextInitializer​

​接口,也就是根據這個類型來加載相關的實作類。

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

  然後加載的關鍵方法是 ​

​getSpringFactoriesInstances()​

​​方法。該方法會加載 ​

​spring.factories​

​​檔案中的key為 ​

​org.springframework.context.ApplicationContextInitializer​

​ 的值。

spring-boot項目下

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer      

spring-boot-autoconfigure項目下

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener      
SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  具體的加載方法為 ​

​getSpringFacotiesInstance()​

​方法,我們進入檢視

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // 擷取目前上下文類加載器
    ClassLoader classLoader = getClassLoader();
    // 擷取到的擴充類名存入set集合中防止重複
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 建立擴充點執行個體
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
  }      

  先進入 ​

​SpringFactoriesLoader.loadFactoryNames(type, classLoader)​

​中具體檢視加載檔案的過程.

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  然後我們來看下 ​

​loadSpringFactories​

​方法

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  通過Debug的方式檢視會更清楚哦

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  通過 ​

​loadSpringFactories​

​​ 方法我們看到把 ​

​spring.factories​

​​檔案中的所有資訊都加載到了記憶體中了,但是我們現在隻需要加載 ​

​ApplicationContextInitializer​

​​類型的資料。這時我們再通過 ​

​getOrDefault()​

​方法來檢視。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  進入方法中檢視

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  然後會根據反射擷取對應的執行個體對象。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析
SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  好了到這其實我們就清楚了 ​

​getSpringFactoriesInstances​

​​方法的作用就是幫我們擷取定義在 ​

​META-INF/spring.factories​

​​檔案中的可以為 ​

​ApplicationContextInitializer​

​​ 的值。并通過反射的方式擷取執行個體對象。然後把執行個體的對象資訊存儲在了SpringApplication的 ​

​initializers​

​屬性中。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

3.setListeners

  清楚了 ​

​setInitializers()​

​​方法的作用後,再看 ​

​setListeners()​

​​方法就非常簡單了,都是調用了 ​

​getSpringFactoriesInstances​

​​方法,隻是傳入的類型不同。也就是要擷取的 ​

​META-INF/spring.factories​

​檔案中定義的不同資訊罷了。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  即加載定義在 ​

​META-INF/spring.factories​

​​檔案中聲明的所有的監聽器,并将擷取後的監聽器存儲在了 ​

​SpringApplication​

​​的 ​

​listeners​

​屬性中。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  預設加載的監聽器為:

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

4.mainApplicationClass

  最後我們來看下 ​

​duduceMainApplicaitonClass()​

​​方法是如何反推導出main方法所在的Class對象的。通過源碼我們可以看到是通過 ​

​StackTrace​

​來實作的。

StackTrace:
我們在學習函數調用時,都知道每個函數都擁有自己的棧空間。
一個函數被調用時,就建立一個新的棧空間。那麼通過函數的嵌套調用最後就形成了一個函數調用堆棧      

  ​

​StackTrace​

​其實就是記錄了程式方法執行的鍊路。通過Debug方式可以更直覺的來呈現。

SpringBoot源碼分析之SpringApplication構造方法核心源碼分析

  那麼相關的調用鍊路我們都可以擷取到,剩下的就隻需要擷取每鍊路判斷執行的方法名稱是否是 ​

​main​

​就可以了。