Spring Boot 的設定可以從很多地方讀取,從程式內 System.setProperty()、命令列 --server.port=80、環境變數 SERVER_PORT、到 application.propertiesapplication-{profile}.properties 都算。當同一個 key 在多個來源都有定義,到底會用哪個?這篇文章整理常見的優先順序,以及容易踩坑的細節。如果想了解的是「怎麼切換 profile」而不是覆蓋順序,可以參考另一篇「指定 Spring Boot 執行環境的多種方式」。

本文以修改內建伺服器的 server.port 為例示範。

優先級總覽

下表整理 Spring Boot 常見 Property Source 由高到低的優先順序(高優先級會覆蓋低優先級)。實際 Spring Boot 文件列出的項目超過 20 個,這邊只挑工作中會用到的。

優先級來源常見用法
1(最高)命令列參數--server.port=80
2SPRING_APPLICATION_JSON環境變數內塞 JSON
3Java System Properties-Dserver.port=80System.setProperty()
4OS 環境變數SERVER_PORT=80
5jar 外的 application-{profile}.properties容器掛載
6jar 內的 application-{profile}.properties打包進去的 profile 檔
7jar 外的 application.properties容器掛載
8(最低)jar 內的 application.properties打包進去的預設檔

記憶法:「越靠近運行時機越優先」、「外部檔案蓋過內部檔案」、「特定 profile 蓋過通用設定」。以下逐項說明。

最高優先:命令列參數

java Application --server.port=80
java -jar Application.jar --server.port=80

啟動 Java 程式時直接帶入的參數,會先傳到 Java 的程式進入點 main(String[] args),再傳到 SpringApplication.run。Spring Boot 把這個來源放在最高優先,方便我們在不改打包檔案的前提下臨時調整某個值。

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

以下寫法也有一樣效果,但這樣寫只是用來讓人更加清楚參數的傳遞,實際上可別這樣寫啊!

public static void main(String[] args) {
    SpringApplication.run(Application.class, new String[]{"--server.port=80"});
}

第二優先:SPRING_APPLICATION_JSON

可以把整包 JSON 設定塞進名為 SPRING_APPLICATION_JSON 的環境變數,Spring Boot 啟動時會解析它。在 CI/CD 或容器化部署時,一次塞多個設定會比一個個環境變數乾淨。

export SPRING_APPLICATION_JSON='{"server":{"port":80},"logging":{"level":{"root":"INFO"}}}'
java -jar Application.jar

第三優先:Java System Properties

透過 -D 啟動參數或在程式內呼叫 System.setProperty(),本質上是同一個 Property Source(都是 Java 的 System Properties)。在 Spring Boot 的優先級表中算同一級,誰覆蓋誰只取決於誰後寫入。

java -Dserver.port=80 Application
java -Dserver.port=80 -jar Application.jar

等價於在程式內手動寫入:

public static void main(String[] args) {
    System.setProperty("server.port", "80");
    SpringApplication.run(Application.class, args);
}

注意 System.setProperty() 必須在 SpringApplication.run() 之前呼叫才會生效,否則 Spring 已經讀取完 System Properties 後再設定就太晚了。

第四優先:OS 環境變數

OS 環境變數是 Docker、Kubernetes、Cloud Run 等容器化部署最常用的方式,因為映像檔本身可以維持不變、只透過環境變數覆蓋特定設定。Spring Boot 會自動把環境變數的命名做轉換,例如 server.port 對應到 SERVER_PORT,規則是「點轉底線、全大寫」。

export SERVER_PORT=80
java -jar Application.jar

常見的對應規則:

Property 名稱環境變數名稱
server.portSERVER_PORT
spring.datasource.urlSPRING_DATASOURCE_URL
spring.profiles.activeSPRING_PROFILES_ACTIVE
logging.level.rootLOGGING_LEVEL_ROOT

第五~第八優先:application.properties 與 application-{profile}.properties

設定檔的部分有兩個維度交叉:「特定 profile 還是通用」、「在 jar 外還是 jar 內」。Spring Boot 把這四種組合排成從高到低四級。

優先級檔案
5jar 外的 application-{profile}.properties
6jar 內的 application-{profile}.properties
7jar 外的 application.properties
8jar 內的 application.properties

「jar 外」是指啟動時跟 jar 同目錄、或 ./config/ 資料夾下的檔案,Spring Boot 會自動掃描。這個設計讓我們能把通用設定打包進去,部署到不同環境時用外部檔案覆蓋特定值,不必每次重新打包。

# application.properties 與 application-{profile}.properties 寫法
server.port=80
# application.yml 與 application-{profile}.yml 寫法
server:
  port: 80

關於 application-{profile} 的命名規則、SPRING_PROFILES_ACTIVE 用法、profile groups 等細節,請參考另一篇文章「指定 Spring Boot 執行環境的多種方式」。

其他來源

Spring Boot 官方文件列的 Property Source 還有更多,本文只挑常用的整理。如果有特殊需求可以參考完整列表:

  • @TestPropertySource:測試類別專用,優先級會比命令列參數還高
  • Devtools 全域設定:~/.spring-boot-devtools.properties
  • JNDI attributes:傳統 Java EE 容器才會用到
  • RandomValuePropertySource:用 ${random.uuid} 之類的語法產生隨機值
  • @PropertySource:在 @Configuration 類別上指定額外設定檔來源(優先級非常低)
  • SpringApplication.setDefaultProperties():當所有來源都沒有時的預設值

完整列表可以參考 Spring Boot 官方文件 Externalized Configuration


Sponsored Links