上一篇《spring-boot啟動源碼分析(二)之SpringApplicationRunListener》
環境介紹:
spring boot版本:2.7.18
主要starter:spring-boot-starter-web
本篇開始講啟動過程中Environment環境準備,Environment是管理所有配置的實例對象,像application.yml、系統屬性、環境變量等配置都可以通過Environment.getProperty(key)獲取
入口如下:
(一)創建ConfigurableEnvironment
首先會從spring.factories中獲取ApplicationContextFactory.class接口實現類,根據webApplicationType會實例化對應的ConfigurableEnvironment(這里是SERVLET,所以是AnnotationConfigServletWebServerApplicationContext.Factory創建的ApplicationServletEnvironment)
ApplicationServletEnvironment的類結構如下:
ApplicationServletEnvironment調用構造方法實例化時,父類AbstractEnvironment構造方法中會有初始化操作:
propertySources是Environment的核心屬性,包含了各個配置源,這里賦值了一個MutablePropertySources,它實際是一個迭代器:
propertyResolver:是屬性解析器,如占位符的解析等
AbstractEnvironment構造器采用類似模板方法將customizePropertySources(propertySources)交給子類實現,子類可以在此方法中加載數據源
可以看到子類StandardServletEnvironment,添加了StubPropertySource,樁配置源,類似打樁,這兩個樁配置源分別是servletConfigInitParams和servletContextInitParams。這兩個在webserver啟動后會被替換成實際的配置源,這里只是個站位,沒有實際的作用。
之后再次調用超類的customizePropertySources:
在StandardEnvironment中會添加兩個重要的配置源,systemProperties和systemEnvironment。
systemProperties對應System.getProperties(),即所有的系統屬性:
systemEnvironment對應的是System.getenv(),即系統的所有環境變量
(二)對Environment進行其他配置
? ? ?
??????? (1)首先添加conversionService:environment中的propertyResolver(配置解析器)配置應用轉換服務,例如NumberToNumberConverterFactory,StringToCharacterConverter等。應該是為propertyResource中的數據解析時進行數據轉換用的
? ? ? ? (2)configurePropertySources:
? ? ? ? 如果SpringApplication中Map<String, Object> defaultProperties不為空,則會創建一個名為“defaultProperties”,實例類型為DefaultPropertiesPropertySource的配置源。
????????java進程傳入的命令行參數加載進配置源中,配置源名稱為commandLineArgs,實例類型為CommandLinePropertySource。此方法為模板方法,可被子類覆蓋實現自己的資源加載方式configureProfiles
? ? ? ? (3)此為空實現,看注釋是說配置此應用程序環境中哪些配置文件處于活動狀態(或默認情況下處于活動狀態)。在配置文件處理過程中,可以通過{@code-spring.profiles.active}屬性激活其他配置文件。
(三)綁定ConfigurationPropertySources
ConfigurationPropertySources.attach(environment);
增加一個名為“configurationProperties”的ConfigurationPropertySourcesPropertySource和environment綁定,實際上是把environment中所有的propertySource(也包括configurationProperties)包裝進一個SpringConfigurationPropertySources實例中,而SpringConfigurationPropertySources是ConfigurationPropertySourcesPropertySource中的一個屬性值,這也意味著configurationProperties可以訪問所有的propertySource,這樣它可以作為配置的統一訪問入口。可以看如下configurationProperties對應實例屬性
所以ConfigurationPropertySourcesPropertySource.getProperty(String name),實際上就是遍歷每個PropertySource,獲取它們的配置,但它對每個PropertySource進行了適配,以便以統一的接口進行獲取配置。
getSource獲取的是SpringConfigurationPropertySources,是一個迭代器,iterator()中會對配置源進行適配,適配成ConfigurationPropertySource
(四)發布environmentPrepared事件
事件的發布流程都差不多,這里不再贅述。主要差別是哪些監聽器會監聽了此事件,并做了什么邏輯處理。這里我們重點說一下這個:
主要有6個監聽器:
代理監聽器可以跳過,沒有實際的邏輯處理。我們關注spring boot自定義的監聽器。
(1)EnvironmentPostProcessorApplicationListener
這里主要是從spring.fatories獲取EnvironmentPostProcessor接口實現類并實例化:
然后調用對應postProcessEnvironment,這些EnvironmentPostProcessor主要是添加不同的數據源配置,如ConfigDataEnvironmentPostProcessor解析我們常用的application.yml作為配置源,詳情可看《Spring boot源碼之EnvironmentPostProcessor》
(2)AnsiOutputApplicationListener
這里主要是根據spring.output.ansi.enabled和spring.output.ansi.console-available,設置AnsiOutput對應的屬性。為輸出到控制臺的日志信息添加 ANSI 轉義碼,以實現彩色輸出。這個用的比較少,我看只在打印banner和logback日志的顏色轉換器中用到了
(3)LoggingApplicationListener
在上一篇發布starting事件中,日志系統進行了初步初始化,在這里則會完成全部的初始化。
a、getLoggingSystemProperties(environment).apply():會將配置文件中配置的日志相關配置設置到系統屬性中,以便后續日志系統初始化時可以從系統屬性讀取。
如:配置文件中如果設置了logging.pattern.console,那么就會設置到系統屬性變量CONSOLE_LOG_PATTERN中。這里還設置了PID以及下面的日志策略
b、initializeEarlyLoggingLevel(environment):初始化早期日志級別,實際只是根據Environment中是否配置debug和trace,設置springBootLogging對應級別
c、initializeSystem(environment, this.loggingSystem, this.logFile):這里是主要的初始化邏輯,如果之前日志已經初始化過,這里會使用spring boot會重新初始化,對日志進行增強。比如在logback中,會使用SpringBootJoranConfigurator,對配置文件進行加載解析,同時新定義了一些解析規則
所以在spring boot中可以使用springProperty,添加屬性,值可以是Environment中配置的屬性值
如上,是配置了一個pid變量,值是Environment中pid屬性對應的屬性值
d、initializeFinalLoggingLevels(environment, this.loggingSystem):初始化日志的最終的日志級別:
如果b步驟設置了springBootLogging,那么spring boot會初始化一些logger,設置其日志級別,如過是debug級別,則下圖圈出來的都會設置是debug級別
另外則是對配置屬性中有logging.level為前綴的設置其對應的日志級別:
如上,則會設置com.example為true。
e、registerShutdownHookIfNecessary(environment, this.loggingSystem)
往SpringApplication注冊日志系統的shutdownHandler,會在shutdown發生時,停止日志上下文。
(4)BackgroundPreinitializer
啟動一個后臺線程去出發早期的初始化,并且有個countDownLatch,只有初始化完成,當ApplicationReadyEvent監聽觸發時,得等線程執行完才會繼續執行,否則等待
(5)FileEncodingApplicationListener
????????當系統設置了spring.mandatory-file-encoding編碼格式時,會判斷是否和System.getProperty("file.encoding")相同,如果不相等則拋出異常阻止系統的啟動,默認沒設置不影響
(五)其他
上面基本已經把環境變量準備完畢,剩下的是一些細微處理:
(1)將defaultProperties配置源移到最后,即查詢資源時最后才查它
(2)SpringApplication相關環境變量綁定
即將spring.main開頭的配置,綁定到對應SpringApplication的屬性中。如spring.main.allow-circular-references=true,那么就會設置SpringApplication的allowCircularReferences值為true.
(3)Environment轉換ConfigurableEnvironment為應用類型的環境,這里是ApplicationServletEnvironment,是ConfigurableEnvironment的子類,不需要轉換