前提
這篇文章是《SpringBoot2.x入門》專輯的第4篇文章,使用的SpringBoot
版本為2.3.1.RELEASE
,JDK
版本為1.8
。
主要介紹SpringBoot
配置檔案一些常用屬性、配置檔案的載入優先順序以及一些和配置相關的注意事項。
關於SpringBoot的配置檔案
一個基於標準的引入了SpringBoot
元件的Maven
專案的結構一般如下:
Root(專案根目錄)
- src
- main
- java
- resources # <-- 這個就是資原始檔的存放目錄
- target
pom.xml
資原始檔存放在src/main/resouces
目錄下,而配置檔案本質上也是資原始檔,所以專案內的配置檔案就存放於該目錄下。從SpringBoot
的屬性源載入器PropertySourceLoader
的實現來看,目前支援Properties
和Yaml
兩種配置檔案型別。兩者各有優勢:Yaml
的配置屬性更靈活,而Properties
的配置不容易出錯(筆者前公司的技術規範中明確了SpringBoot
應用必須使用Properties
配置檔案,因為運維或者開發同事曾因為生產配置使用了Yaml
格式的檔案,編輯期間因為空格問題導致了嚴重的生產故障)。下文會使用Properties
配置檔案作為示例。
SpringBoot
的配置檔案使用了profile
(profile
本身就有剖面、配置檔案的含義,下面會把profile
作為一個專有名詞使用)的概念,可以類比為區分不同環境的識別符號,一個SpringBoot
應用允許使用多個profile
,所以配置檔案的格式必須為application-${profile}.檔案字尾
,例如:
src/main/resources
- application.properties
- application-dev.properties # <-- profile = dev,開發環境配置
- application-test.properties # <-- profile = test,測試環境配置
其中不帶profile
標識的application.properties
,可以理解為主配置檔案,也就是SpringBoot
的配置檔案其實有繼承關係,專案啟動時,主配置檔案無論如何都會優先載入,然後被啟用的profile
標識的配置檔案才會載入,可以通過屬性spring.profiles.active
指定啟用哪一個profile
配置檔案,如:
# 指定載入application-dev.properties
spring.profiles.active=dev
# 或者同時載入application-dev.properties和application-test.properties
spring.profiles.active=dev,test
spring.profiles.active
一般可以在主配置檔案application.properties
中指定,獲取通過啟動命令引數指定(java -jar -Dspring.profiles.active=prod app.jar
或者java -jar app.jar --spring.profiles.active=prod
)。
可以通過自動裝配org.springframework.core.env.Environment
,通過Environment#getActiveProfiles()
獲取當前啟用的profile
陣列,例如:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import java.util.Arrays;
@Slf4j
@SpringBootApplication
public class Ch2Application implements CommandLineRunner {
@Autowired
private Environment environment;
public static void main(String[] args) {
SpringApplication.run(Ch2Application.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("Active profiles:{}", Arrays.toString(environment.getActiveProfiles()));
}
}
執行結果如下:
這裡用到了CommandLineRunner
介面作為展示,後面的文章會介紹該介面的使用方式。
常用的基本配置屬性
一般情況下會引入spring-boot-starter-web
開發web
專案,有幾個基本配置筆者認為是必須的。主配置檔案application.properties
中應該標識應用名和預設選用的profile
,例如API
閘道器的主配置檔案如下:
spring.application.name=api-gateway
spring.profiles.active=dev
此外,主配置中間中應該配置一些不容易變動的屬性,例如Mybatis
的Mapper
掃描路徑、模板引擎Freemarker
的配置屬性等等。而profile
標識的配置檔案中,應該配置一些跟隨環境變化的配置或者經常更變的屬性,例如MySQL
資料來源的配置、Redis
的連線配置等等,以便通過spring.profiles.active
直接切換不同環境的中介軟體連線屬性或者第三方配置。在Hello World
型別的專案中,一般新增server.port
指定容器的啟動埠,如application-dev.properties
的內容如下:
server.port=8081
配置檔案載入優先順序與屬性覆蓋
除了主配置檔案會優先profile
標識的配置檔案載入之外,SpringBoot
還支援通過檔案系統載入配置檔案,這些配置檔案不一定在專案內(準確來說是專案編譯之後打出來的包內),還可以存在於特定的磁碟路徑中。這一點可以參考SpringBoot
官方文件2.Externalized Configuration
一節:
預設的配置檔案載入優先順序順序是:
file:./config/
(專案部署包所在目錄的同級config
目錄下的application-[profile].[properties,yaml]
檔案)file:./config/*/
(專案部署包所在目錄的同級config
目錄下的任意子目錄中的application-[profile].[properties,yaml]
檔案)file:./
(專案部署包所在目錄的application-[profile].[properties,yaml]
檔案)classpath:/config/
(專案部署包內類路徑下的config
目錄下的application-[profile].[properties,yaml]
檔案)classpath:/
(專案部署包內類路徑下的application-[profile].[properties,yaml]
檔案)
眼尖的夥伴可能會發現,在專案中的resources
目錄下新增的配置檔案的載入優先順序是最低的(打包後相當於第5條)。可以通過spring.config.location
屬性覆蓋上面的順序,如spring.config.location=classpath:/,classpath:/config/
,一般不建議改變預設的配置順序,除非有特殊的使用場景。
另外,還可以通過spring.config.additional-location
屬性指定額外附加的搜尋配置檔案的路徑,並且優先順序比預設的配置順序要高,假如只配置了spring.config.additional-location=classpath:/custom-config/,file:./custom-config/
,那麼配置檔案載入優先順序順序是:
file:./custom-config/
(專案部署包所在目錄的同級custom-config
目錄下的application-[profile].[properties,yaml]
檔案)classpath:custom-config/
(專案部署包內類路徑下的custom-config
目錄下的application-[profile].[properties,yaml]
檔案)file:./config/
(專案部署包所在目錄的同級config
目錄下的application-[profile].[properties,yaml]
檔案)file:./config/*/
(專案部署包所在目錄的同級config
目錄下的任意子目錄中的application-[profile].[properties,yaml]
檔案)file:./
(專案部署包所在目錄的application-[profile].[properties,yaml]
檔案)classpath:/config/
(專案部署包內類路徑下的config
目錄下的application-[profile].[properties,yaml]
檔案)classpath:/
(專案部署包內類路徑下的application-[profile].[properties,yaml]
檔案)
基於這個特性,在不對接配置中心的前提下,可以讓運維夥伴在生產伺服器上先配置好服務所需的生產環境的配置檔案:
# 假設這個是生產伺服器的檔案路徑
/data/apps/api-gateway
- api-gateway.jar
- config
- application-prod.properties
在編寫啟動指令碼的時候只需指定profile
為prod
即可,應用會讀取/data/apps/api-gateway/config/application-prod.properties
的屬性,這樣就能避免生產配置或者敏感屬性洩漏給開發人員。
這裡還有一個比較重要的問題就是:如果在多種路徑下的配置檔案定義了同一個屬性,那麼屬性會依照一個優先順序順序進行覆蓋。因為SpringBoot
除了配置檔案,還支援命令列、JNDI
屬性、系統屬性等等,如果全部列舉會比較複雜,這裡按照目前分析過的內容列舉這個優先順序順序:
- 命令列中的屬性引數。
- 專案部署包之外的
application-profile.[properties,yaml]
檔案。 - 專案部署包內的
application-profile.[properties,yaml]
檔案。 - 專案部署包之外的
application.[properties,yaml]
檔案。 - 專案部署包內的
application.[properties,yaml]
檔案。
舉個例子,假如啟動引數中新增--app.author=throwable
,配置檔案application.properties
中新增屬性app.author=throwable-x
,而配置檔案application-dev.properties
中新增屬性app.author=throwable-y
,那麼使用profile=dev
啟動應用的時候,優先獲取到的是屬性app.author=throwable
:
如果看過SpringBoot屬性載入的原始碼可知,其實屬性優先順序的思路在設計屬性載入模組的時候正好相反,所有的配置檔案都會進行解析,構成一個複合的PropertySource,後解析的引數總是在頂層,然後獲取屬性的時候,總是先從頂層獲取。
自定義配置屬性與IDE親和性
有時候需要配置自定義屬性,會出現在IDE
中會無法識別而"標黃"的場景。這個時候可以應用IDE
親和性。在主流的IDE
如Eclipse
和IntelliJ IDEA
中,只需要引入SpringBoot
的屬性後設資料描述檔案(spring-configuration-metadata.json
或者additional-spring-configuration-metadata.json
),即可讓IDE
識別,提供目錄引導跳轉的功能,不再"標黃"。具體的做法是在專案的resources/META-INF
目錄中引入屬性後設資料描述檔案,然後編寫屬性描述即可:
// resources/META-INF/spring-configuration-metadata.json
{
"properties": [
{
"name": "app.author",
"type": "java.lang.String",
"description": "The author of app."
}
]
}
spring-configuration-metadata.json
檔案的格式可以參考SpringBoot
多個starter
中已經存在的檔案,完成這一點,程式碼潔癖患者或者強迫症患者會感覺良好。
小結
這篇文章簡單總結了配置檔案載入的優先順序順序和配置屬性的覆蓋優先順序順序,這兩點需要完全掌握,可以自行通過一些例子改變一下配置檔案進行熟悉。配置屬性覆蓋的問題很容易導致生產故障,如果掌握了本節的內容,對於SpringBoot
配置屬性方面的問題應該可以快速定位和解決。程式碼倉庫:
(本文完 c-2-d e-a-20200705)
技術公眾號《Throwable文摘》(id:throwable-doge),不定期推送筆者原創技術文章(絕不抄襲或者轉載):