天天看點

SpringBoot啟動如何加載application.yml配置檔案

一、前言

在spring時代配置檔案的加載都是通過web.xml配置加載的(Servlet3.0之前),可能配置方式有所不同,但是大多數都是通過指定路徑的檔案名的形式去告訴spring該加載哪個檔案;

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/application*.xml</param-value>
</context-param>      

而到了springboot時代,我們發現原來熟悉的web.xml已不複存在,但是springboot卻依然可以找到預設的配置檔案(application.yml),那它是如何實作的呢?今天我們就一起來探究一下springboot自動加載配置檔案的機制!

看完本篇文章你将了解到:

  1. springboot什麼時候加載配置檔案
  2. springboot通過哪個類加載配置檔案
  3. springboot自動加載配置檔案流程
  4. 激活檔案優先級
  5. 檔案加載路徑優先級
  6. 檔案字尾優先級

二、提出猜想

我們知道在使用springboot中我們隻要在resources下面建立一個application.yml檔案他就會自動加載,那是不是springboot預設在哪裡配置了這個路徑和檔案名?

三、驗證猜想

為了證明我們的猜想,我們可以通過檢視springboot項目源碼,跟着debug一步一步走;

這裡我使用的是springboot2.0版本,2.0與1.5版本比較啟動的大體流程是一樣的,隻不過在一些實作中有所差異;

1.啟動流程

要知道springboot如何加載配置檔案,就需要了解它的啟動流程:

我們從main方法進入,大概的調用流程如下:

DemoApplication.main->SpringApplication.run->new SpringApplication().run
SpringBoot啟動如何加載application.yml配置檔案

其實啟動的主要過程都在​

​new SpringApplication().run();​

  • new SpringApplication():建立SpringApplication執行個體,負責加載配置一些基本的環境變量、資源、構造器、監聽器
  • run():負責springboot整個啟動過程,包括加載建立環境、列印banner、配置檔案、配置應用上下文,加載bean等等sb整個生命周期幾乎都在run方法中;

今天我們的主題是sb如何加載配置檔案,是以着重講解加載配置檔案和之前的操作原理和源碼,其他的功能以後有機會再和大家一起研究,下面我們來看看​

​new SpringApplication()​

​做了什麼操作;

2.建立SpringApplication執行個體

/**
 * 建立一個SpringApplication實體,應用程式上下文将從指定的主源文檔加載bean以擷取詳細資訊,
 * 這個執行個體可以在調用之前自定義
 * @param resourceLoader
 * @param primarySources
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  //使用的資源加載器
  this.resourceLoader = resourceLoader;
  //主要的bean資源 primarySources【在這裡是啟動類所在的.class】,不能為null,如果為null,抛異常
  Assert.notNull(primarySources, "PrimarySources must not be null");
  //啟動類的執行個體數組轉化成list,放在LinkedHashSet集合中
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  /**
   * 建立應用類型,不同應用程式類型,建立不同的環境
   * springboot1.5 隻有兩種類型:web環境和非web環境
   * springboot2.0 有三種應用類型:WebApplicationType
   * NONE:不需要再web容器的環境下運作,也就是普通的工程
   * SERVLET:基于servlet的Web項目
   * REACTIVE:響應式web應用reactive web Spring5版本的新特性
   */
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  /**
   * 每一個initailizer都是一個實作了ApplicationContextInitializer接口的執行個體。
   * ApplicationContextInitializer是Spring IOC容器中提供的一個接口: void initialize(C applicationContext);
   * 這個方法它會在ConfigurableApplicationContext的refresh()方法調用之前被調用(prepareContext方法中調用),
   * 做一些容器的初始化工作。
   */
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  /**
   * Springboot整個生命周期在完成一個階段的時候都會通過事件推送器(EventPublishingRunListener)産生一個事件(ApplicationEvent),
   * 然後再周遊每個監聽器(ApplicationListener)以比對事件對象,這是一種典型的觀察者設計模式的實作
   * 具體事件推送原理請看:sb事件推送機制圖
   */
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  // 指定main函數啟動所在的類,即啟動類BootApplication.class
  this.mainApplicationClass = deduceMainApplicationClass();
}      

我們來大概的看下ApplicationListener的一些實作類以及他們具體的功能簡介

SpringBoot啟動如何加載application.yml配置檔案

這些監聽器的實作類都是在spring.factories檔案中配置好的,代碼中通過​

​getSpringFactoriesInstances​

​方法擷取,這種機制叫做SPI機制:通過本地的注冊發現擷取到具體的實作類,輕松可插拔。

SpringBoot啟動如何加載application.yml配置檔案

SpringBoot預設情況下提供了兩個spring.factories檔案,分别是:

spring-boot-2.0.2.RELEASE.jar

spring-boot-autoconfigure-2.0.2.RELEASE.jar

SpringBoot啟動如何加載application.yml配置檔案

概括來說在建立SpringApplication執行個體的時候,sb會加載一些初始化和啟動的參數與類,如同跑步比賽時的等待發令槍的階段;

3.run方法

(1)、事件推送原理

SB啟動過程中分多個階段或者說是多個步驟,每完成一步就會産生一個事件,并調用對應事件的監聽器,這是一種标準的觀察者模式,這在啟動的過程中有很好的擴充性,下面我們來看看sb的事件推送原理:

SpringBoot事件推送原理圖:

SpringBoot啟動如何加載application.yml配置檔案

(2)、run方法整體流程簡述

/**
 * 運作應用程式,建立并重新整理一個新的應用程式上下文
 *
 * @param args
 * @return
 */
public ConfigurableApplicationContext run(String... args) {
  /**
   *  StopWatch: 簡單的秒表,允許定時的一些任務,公開每個指定任務的總運作時間和運作時間。
   *  這個對象的設計不是線程安全的,沒有使用同步。SpringApplication是在單線程環境下,使用安全。
   */
  StopWatch stopWatch = new StopWatch();
  // 設定目前啟動的時間為系統時間startTimeMillis = System.currentTimeMillis();
  stopWatch.start();
  // 建立一個應用上下文引用
  ConfigurableApplicationContext context = null;
  // 異常收集,報告啟動異常
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  /**
   * 系統設定headless模式(一種缺乏顯示裝置、鍵盤或滑鼠的環境下,比如伺服器),
   * 通過屬性:java.awt.headless=true控制
   */
  configureHeadlessProperty();
  /*
   * 擷取事件推送監器,負責産生事件,并調用支某類持事件的監聽器
   * 事件推送原理看上面的事件推送原理圖
   */
  SpringApplicationRunListeners listeners = getRunListeners(args);
  /**
   * 釋出一個啟動事件(ApplicationStartingEvent),通過上述方法調用支援此事件的監聽器
   */
  listeners.starting();
  try {
    // 提供對用于運作SpringApplication的參數的通路。取預設實作
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    /**
     * 建構容器環境,這裡加載配置檔案
     */
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    // 對環境中一些bean忽略配置
    configureIgnoreBeanInfo(environment);
    // 日志控制台列印設定
    Banner printedBanner = printBanner(environment);
    // 建立容器
    context = createApplicationContext();
    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);
    /**
     * 準備應用程式上下文
     * 追蹤源碼prepareContext()進去我們可以發現容器準備階段做了下面的事情:
     * 容器設定配置環境,并且監聽容器,初始化容器,記錄啟動日志,
     * 将給定的singleton對象添加到此工廠的singleton緩存中。
     * 将bean加載到應用程式上下文中。
     */
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    /**
     * 重新整理上下文
     * 1、同步重新整理,對上下文的bean工廠包括子類的重新整理準備使用,初始化此上下文的消息源,注冊攔截bean的處理器,檢查偵聽器bean并注冊它們,執行個體化所有剩餘的(非延遲-init)單例。
     * 2、異步開啟一個同步線程去時時監控容器是否被關閉,當關閉此應用程式上下文,銷毀其bean工廠中的所有bean。
     * 。。。底層調refresh方法代碼量較多
     */
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    // stopwatch 的作用就是記錄啟動消耗的時間,和開始啟動的時間等資訊記錄下來
    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);
  }
  try {
    // 釋出一個運作中的事件
    listeners.running(context);
  }
  catch (Throwable ex) {
    // 啟動異常,裡面會釋出一個失敗的事件
    handleRunFailure(context, ex, exceptionReporters, null);
    throw new IllegalStateException(ex);
  }
  return context;
}      

(3)、建構容器環境

在:run方法中的​

​ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);​

​是準備環境,裡面會加載配置檔案;

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
  // 建立一個配置環境,根據前面定義的應用類型定義不同的環境
  ConfigurableEnvironment environment = getOrCreateEnvironment();
  // 将配置參數設定到配置環境中
  configureEnvironment(environment, applicationArguments.getSourceArgs());
  /**
   * 釋出一個環境裝載成功的事件,并調用支援此事件的監聽器
   * 這其中就有我們今天的主角:配置檔案加載監聽器(ConfigFileApplicationListener)
   */
  listeners.environmentPrepared(environment);
  // 将配置環境綁定到應用程式
  bindToSpringApplication(environment);
  if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader())
        .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
  }
  ConfigurationPropertySources.attach(environment);
  return environment;
}      

(4)、ConfigFileApplicationListener類介紹

sb就是通過ConfigFileApplicationListener 這個類來加載配置檔案的,這個類同樣是一個監聽器,我們來看看他的繼承類圖:

SpringBoot啟動如何加載application.yml配置檔案

再讓我們來看看這個類具體都有哪些方法:

SpringBoot啟動如何加載application.yml配置檔案

最後我們來看看這個類有哪些需要注意的字段:

SpringBoot啟動如何加載application.yml配置檔案

(5)、ConfigFileApplicationListener類加載配置檔案

我們從ConfigFileApplicationListener.onApplicationEvent開始,一直往下看方法鍊,發現最後是load方法去具體怎麼加載配置檔案的

SpringBoot啟動如何加載application.yml配置檔案
SpringBoot啟動如何加載application.yml配置檔案

激活配置檔案與預設配置檔案的優先級:

我們在使用中經常會根據不同的環境根據​

​spring.profiles.active​

​屬性來定義不同的配置檔案:

  • application-dev.properties
  • application-test.properties
  • application-prod.properties

但同時我們會建立一個預設的配置檔案:​

​application.properties​

​,那自定義環境的配置檔案與預設的配置檔案的優先級是哪個高呢?

SpringBoot啟動如何加載application.yml配置檔案

看圖檔我們可知他們加載的先後順序(注意:後加載會覆寫前加載的檔案):

  • application-xxx.properties
  • application.properties

配置檔案路徑的優先級:

我們從屬性:​

​DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/​

​可以看出檔案路徑的先後順序(注意:後加載的會覆寫先加載的):

  • classpath:/
  • classpath:/config/
  • file:./
  • file:./config/

配置檔案的優先級:

我們從這個類中的字段:propertySourceLoaders可以看出有兩個Loader,請各位看官看圖:

SpringBoot啟動如何加載application.yml配置檔案
SpringBoot啟動如何加載application.yml配置檔案
SpringBoot啟動如何加載application.yml配置檔案

我們從上面兩張圖中可以看出,每個Loader會加載兩種字尾名的檔案,加起來就是4種,又因為是數組類型,是以也會有先後順序,是以加載配置檔案的先後順序就是(後加載覆寫先加載的):

  • properties
  • xml
  • yml
  • yaml

最後查找的具體路徑:​

​location + name + "-" + profile + "." + ext​

這裡我們介紹了三種優先級:

  1. active與預設優先級
  2. 檔案路徑優先級
  3. 檔案字尾優先級

    未完待續。。。

四、提問

springboot學習遺留問題,

1.active和預設的誰覆寫誰

2.flter差別

3.多個配置檔案如何覆寫

更多Java優質文章,請關注豬哥微信公衆号:豬哥Java!

繼續閱讀