外部組態

Spring Boot 讓您可以外部化您的組態,以便您可以在不同的環境中使用相同的應用程式程式碼。您可以使用各種外部組態來源,包括 Java 屬性檔案、YAML 檔案、環境變數和命令列引數。

屬性值可以使用 @Value 註解直接注入到您的 beans 中,透過 Spring 的 Environment 抽象存取,或者透過 @ConfigurationProperties 繫結到結構化物件

Spring Boot 使用非常特定的 PropertySource 順序,旨在允許合理地覆寫值。後面的屬性來源可以覆寫先前來源中定義的值。來源會依以下順序考量:

  1. 預設屬性(透過設定 SpringApplication.setDefaultProperties 指定)。

  2. @Configuration 類別上的 @PropertySource 註解。請注意,在應用程式上下文重新整理之前,這些屬性來源不會新增到 Environment。這對於組態某些屬性(例如 logging.*spring.main.*)來說太晚了,因為它們會在重新整理開始之前讀取。

  3. 組態資料(例如 application.properties 檔案)。

  4. 一個 RandomValuePropertySource,其屬性僅在 random.* 中。

  5. 作業系統環境變數。

  6. Java 系統屬性 (System.getProperties())。

  7. 來自 java:comp/env 的 JNDI 屬性。

  8. ServletContext 初始化參數。

  9. ServletConfig 初始化參數。

  10. 來自 SPRING_APPLICATION_JSON 的屬性(嵌入在環境變數或系統屬性中的內嵌 JSON)。

  11. 命令列引數。

  12. 您測試中的 properties 屬性。在 @SpringBootTest用於測試應用程式特定切片的測試註解 中可用。

  13. 您測試中的 @DynamicPropertySource 註解。

  14. 您測試中的 @TestPropertySource 註解。

  15. 當 devtools 啟用時,Devtools 全域設定屬性$HOME/.config/spring-boot 目錄中。

組態資料檔案會依以下順序考量:

  1. 封裝在您的 jar 內的應用程式屬性 (application.properties 和 YAML 變體)。

  2. 封裝在您的 jar 內的特定設定檔應用程式屬性 (application-{profile}.properties 和 YAML 變體)。

  3. 在您封裝的 jar 外部的應用程式屬性 (application.properties 和 YAML 變體)。

  4. 在您封裝的 jar 外部的特定設定檔應用程式屬性 (application-{profile}.properties 和 YAML 變體)。

建議整個應用程式堅持使用一種格式。如果您在相同位置同時擁有 .properties 和 YAML 格式的組態檔案,則 .properties 優先。
如果您使用環境變數而不是系統屬性,則大多數作業系統不允許使用句點分隔的鍵名稱,但您可以使用底線代替(例如,SPRING_CONFIG_NAME 而不是 spring.config.name)。請參閱 從環境變數繫結 以取得詳細資訊。
如果您的應用程式在 servlet 容器或應用程式伺服器中執行,則可以使用 JNDI 屬性(在 java:comp/env 中)或 servlet 上下文初始化參數來代替或補充環境變數或系統屬性。

為了提供具體的範例,假設您開發一個使用 name 屬性的 @Component,如下列範例所示:

  • Java

  • Kotlin

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

	@Value("${name}")
	private String name;

	// ...

}
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class MyBean {

	@Value("\${name}")
	private val name: String? = null

	// ...

}

在您的應用程式類別路徑(例如,在您的 jar 內部)中,您可以有一個 application.properties 檔案,為 name 提供合理的預設屬性值。在新環境中執行時,可以在您的 jar 外部提供 application.properties 檔案,以覆寫 name。對於一次性測試,您可以使用特定的命令列切換來啟動(例如,java -jar app.jar --name="Spring")。

envconfigprops 端點可用於判斷屬性為何具有特定值。您可以使用這兩個端點來診斷非預期的屬性值。請參閱 生產就緒功能 章節以取得詳細資訊。

存取命令列屬性

預設情況下,SpringApplication 會將任何命令列選項引數(即以 -- 開頭的引數,例如 --server.port=9000)轉換為 property,並將其新增到 Spring Environment。如先前所述,命令列屬性始終優先於基於檔案的屬性來源。

如果您不希望將命令列屬性新增到 Environment,您可以使用 SpringApplication.setAddCommandLineProperties(false) 來停用它們。

JSON 應用程式屬性

環境變數和系統屬性通常具有限制,這表示某些屬性名稱無法使用。為了協助解決此問題,Spring Boot 允許您將屬性區塊編碼為單個 JSON 結構。

當您的應用程式啟動時,任何 spring.application.jsonSPRING_APPLICATION_JSON 屬性都將被解析並新增到 Environment

例如,SPRING_APPLICATION_JSON 屬性可以在 UN*X shell 的命令列中作為環境變數提供:

$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar

在前面的範例中,您最終會在 Spring Environment 中得到 my.name=test

相同的 JSON 也可以作為系統屬性提供:

$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar

或者您可以透過使用命令列引數來提供 JSON:

$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'

如果您部署到傳統應用程式伺服器,您也可以使用名為 java:comp/env/spring.application.json 的 JNDI 變數。

雖然來自 JSON 的 null 值將新增到產生的屬性來源,但 PropertySourcesPropertyResolvernull 屬性視為遺失值。這表示 JSON 無法使用 null 值覆寫來自較低順序屬性來源的屬性。

外部應用程式屬性

當您的應用程式啟動時,Spring Boot 將自動尋找並載入來自以下位置的 application.propertiesapplication.yaml 檔案:

  1. 從類別路徑

    1. 類別路徑根目錄

    2. 類別路徑 /config 套件

  2. 從目前目錄

    1. 目前目錄

    2. 目前目錄中的 config/ 子目錄

    3. config/ 子目錄的直接子目錄

清單依優先順序排序(較低項目的值覆寫較早項目的值)。從載入的檔案中取得的文件會作為 PropertySources 新增到 Spring Environment

如果您不喜歡將 application 作為組態檔案名稱,您可以透過指定 spring.config.name 環境屬性來切換到另一個檔案名稱。例如,若要尋找 myproject.propertiesmyproject.yaml 檔案,您可以如下執行您的應用程式:

$ java -jar myproject.jar --spring.config.name=myproject

您也可以透過使用 spring.config.location 環境屬性來參考明確的位置。此屬性接受要檢查的一個或多個位置的逗號分隔清單。

以下範例顯示如何指定兩個不同的檔案:

$ java -jar myproject.jar --spring.config.location=\
	optional:classpath:/default.properties,\
	optional:classpath:/override.properties
如果 位置是選用的 且您不介意它們不存在,請使用前綴 optional:
spring.config.namespring.config.locationspring.config.additional-location 很早就會使用,以判斷必須載入哪些檔案。它們必須定義為環境屬性(通常是作業系統環境變數、系統屬性或命令列引數)。

如果 spring.config.location 包含目錄(而不是檔案),它們應該以 / 結尾。在執行階段,它們將附加從 spring.config.name 產生的名稱,然後再載入。在 spring.config.location 中指定的檔案會直接匯入。

目錄和檔案位置值也會展開以檢查特定設定檔檔案。例如,如果您具有 classpath:myconfig.propertiesspring.config.location,您也會找到適當的 classpath:myconfig-<profile>.properties 檔案被載入。

在大多數情況下,您新增的每個 spring.config.location 項目都將參考單個檔案或目錄。位置會依定義的順序處理,後面的位置可以覆寫較早位置的值。

如果您有複雜的位置設定,並且您使用特定設定檔組態檔案,則您可能需要提供進一步的提示,以便 Spring Boot 知道它們應該如何分組。位置群組是同時考量的所有位置的集合。例如,您可能想要將所有類別路徑位置分組,然後將所有外部位置分組。位置群組內的項目應以 ; 分隔。請參閱 特定設定檔檔案 章節中的範例以取得更多詳細資訊。

使用 spring.config.location 組態的位置會取代預設位置。例如,如果 spring.config.location 組態的值為 optional:classpath:/custom-config/,optional:file:./custom-config/,則考量的完整位置集為:

  1. optional:classpath:custom-config/

  2. optional:file:./custom-config/

如果您希望新增額外的位置,而不是取代它們,您可以使用 spring.config.additional-location。從其他位置載入的屬性可以覆寫預設位置中的屬性。例如,如果 spring.config.additional-location 組態的值為 optional:classpath:/custom-config/,optional:file:./custom-config/,則考量的完整位置集為:

  1. optional:classpath:/;optional:classpath:/config/

  2. optional:file:./;optional:file:./config/;optional:file:./config/*/

  3. optional:classpath:custom-config/

  4. optional:file:./custom-config/

這種搜尋順序讓您可以在一個組態檔案中指定預設值,然後在另一個自訂位置中的不同檔案中選擇性地覆寫這些值。您可以在預設位置之一的 application.properties(或您使用 spring.config.name 選擇的任何其他基本名稱)中為您的應用程式提供預設值。然後,可以使用位於自訂位置之一的不同檔案在執行階段覆寫這些預設值。

選用位置

預設情況下,當指定的組態資料位置不存在時,Spring Boot 將拋出 ConfigDataLocationNotFoundException,並且您的應用程式將不會啟動。

如果您想要指定一個位置,但不介意它是否始終存在,您可以使用 optional: 前綴。您可以將此前綴與 spring.config.locationspring.config.additional-location 屬性以及 spring.config.import 宣告一起使用。

例如,spring.config.import 值為 optional:file:./myconfig.properties 允許您的應用程式啟動,即使 myconfig.properties 檔案遺失。

如果您想要忽略所有 ConfigDataLocationNotFoundExceptions 並始終繼續啟動您的應用程式,您可以使用 spring.config.on-not-found 屬性。使用 SpringApplication.setDefaultProperties(…​) 或使用系統/環境變數將值設定為 ignore

萬用字元位置

如果組態檔案位置包含 * 字元作為最後的路徑段,則它被視為萬用字元位置。當載入組態時,萬用字元會展開,以便也檢查直接子目錄。當 Kubernetes 等環境中存在多個組態屬性來源時,萬用字元位置特別有用。

例如,如果您有一些 Redis 組態和一些 MySQL 組態,您可能想要將這兩個組態分開,同時要求這兩者都存在於 application.properties 檔案中。這可能會導致兩個單獨的 application.properties 檔案掛載在不同的位置,例如 /config/redis/application.properties/config/mysql/application.properties。在這種情況下,具有 config/*/ 的萬用字元位置將導致這兩個檔案都被處理。

預設情況下,Spring Boot 在預設搜尋位置中包含 config/*/。這表示將搜尋您的 jar 外部 /config 目錄的所有子目錄。

您可以將萬用字元位置與 spring.config.locationspring.config.additional-location 屬性一起使用。

萬用字元位置必須僅包含一個 *,並以 */ 結尾以表示作為目錄的搜尋位置,或以 */<filename> 結尾以表示作為檔案的搜尋位置。具有萬用字元的位置會根據檔案名稱的絕對路徑按字母順序排序。
萬用字元位置僅適用於外部目錄。您無法在 classpath: 位置中使用萬用字元。

特定設定檔檔案

除了 application 屬性檔案之外,Spring Boot 也會嘗試使用命名慣例 application-{profile} 載入特定設定檔檔案。例如,如果您的應用程式啟用了名為 prod 的設定檔並使用 YAML 檔案,則將考量 application.yamlapplication-prod.yaml

特定設定檔屬性從與標準 application.properties 相同的位置載入,特定設定檔檔案始終覆寫非特定檔案。如果指定了多個設定檔,則適用後勝策略。例如,如果設定檔 prod,livespring.profiles.active 屬性指定,則 application-prod.properties 中的值可以被 application-live.properties 中的值覆寫。

後勝策略適用於位置群組層級。classpath:/cfg/,classpath:/ext/spring.config.location 將不具有與 classpath:/cfg/;classpath:/ext/ 相同的覆寫規則。

例如,繼續我們上面的 prod,live 範例,我們可能會有以下檔案:

/cfg
  application-live.properties
/ext
  application-live.properties
  application-prod.properties

當我們有 classpath:/cfg/,classpath:/ext/spring.config.location 時,我們會先處理所有 /cfg 檔案,然後再處理所有 /ext 檔案:

  1. /cfg/application-live.properties

  2. /ext/application-prod.properties

  3. /ext/application-live.properties

當我們改為具有 classpath:/cfg/;classpath:/ext/(使用 ; 分隔符)時,我們會在同一層級處理 /cfg/ext

  1. /ext/application-prod.properties

  2. /cfg/application-live.properties

  3. /ext/application-live.properties

Environment 具有一組預設設定檔(預設情況下為 [default]),如果未設定任何活動設定檔,則會使用這些設定檔。換句話說,如果未明確啟用設定檔,則會考量來自 application-default 的屬性。

屬性檔案只會載入一次。如果您已直接匯入特定設定檔屬性檔案,則不會再次匯入它。

匯入其他資料

應用程式屬性可以使用 spring.config.import 屬性從其他位置匯入更多組態資料。匯入會在發現時處理,並被視為緊接在宣告匯入的文件下方插入的其他文件。

例如,您的類別路徑 application.properties 檔案中可能具有以下內容:

  • Properties

  • YAML

spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
spring:
  application:
    name: "myapp"
  config:
    import: "optional:file:./dev.properties"

這將觸發匯入目前目錄中的 dev.properties 檔案(如果存在此檔案)。來自匯入的 dev.properties 的值將優先於觸發匯入的檔案。在上述範例中,dev.properties 可以將 spring.application.name 重新定義為不同的值。

無論宣告多少次,匯入都只會匯入一次。匯入在 properties/yaml 檔案的單個文件中定義的順序無關緊要。例如,以下兩個範例產生相同的結果:

  • Properties

  • YAML

spring.config.import=my.properties
my.property=value
spring:
  config:
    import: "my.properties"
my:
  property: "value"
  • Properties

  • YAML

my.property=value
spring.config.import=my.properties
my:
  property: "value"
spring:
  config:
    import: "my.properties"

在上述兩個範例中,來自 my.properties 檔案的值將優先於觸發其匯入的檔案。

可以在單個 spring.config.import 鍵下指定多個位置。位置將依定義的順序處理,後續匯入優先。

在適當的情況下,也會考量特定設定檔變體以進行匯入。上面的範例將匯入 my.properties 以及任何 my-<profile>.properties 變體。

Spring Boot 包含可插拔 API,允許支援各種不同的位置位址。預設情況下,您可以匯入 Java Properties、YAML 和 組態樹

協力廠商 jar 可以為其他技術提供支援(不要求檔案是本機的)。例如,您可以想像組態資料來自外部儲存區,例如 Consul、Apache ZooKeeper 或 Netflix Archaius。

如果您想要支援自己的位置,請參閱 org.springframework.boot.context.config 套件中的 ConfigDataLocationResolverConfigDataLoader 類別。

匯入無副檔名的檔案

某些雲端平台無法將檔案副檔名新增到磁碟區掛載的檔案。若要匯入這些無副檔名的檔案,您需要給 Spring Boot 一個提示,以便它知道如何載入它們。您可以透過在方括號中放置副檔名提示來完成此操作。

例如,假設您有一個 /etc/config/myconfig 檔案,您希望將其作為 yaml 匯入。您可以使用以下內容從您的 application.properties 匯入它:

  • Properties

  • YAML

spring.config.import=file:/etc/config/myconfig[.yaml]
spring:
  config:
    import: "file:/etc/config/myconfig[.yaml]"

使用組態樹

在雲端平台(例如 Kubernetes)上執行應用程式時,您經常需要讀取平台提供的組態值。為此目的使用環境變數並不少見,但這可能存在缺點,尤其是在應該保密值的情況下。

除了環境變數之外,許多雲端平台現在允許您將組態對應到掛載的資料卷中。例如,Kubernetes 可以將 ConfigMapsSecrets 卷掛載。

有兩種常見的卷掛載模式可以使用

  1. 單一檔案包含完整的屬性集(通常寫為 YAML)。

  2. 多個檔案寫入到目錄樹中,檔名成為「鍵 (key)」,內容成為「值 (value)」。

對於第一種情況,您可以直接使用 spring.config.import 匯入 YAML 或 Properties 檔案,如上方所述。對於第二種情況,您需要使用 configtree: 前綴,以便 Spring Boot 知道它需要將所有檔案公開為屬性。

舉例來說,假設 Kubernetes 已經掛載了以下卷

etc/
  config/
    myapp/
      username
      password

username 檔案的內容將會是組態值,而 password 的內容將會是密碼。

若要匯入這些屬性,您可以將以下內容新增到您的 application.propertiesapplication.yaml 檔案中

  • Properties

  • YAML

spring.config.import=optional:configtree:/etc/config/
spring:
  config:
    import: "optional:configtree:/etc/config/"

然後,您可以像平常一樣從 Environment 存取或注入 myapp.usernamemyapp.password 屬性。

組態樹下的資料夾和檔案名稱會構成屬性名稱。在上述範例中,若要以 usernamepassword 存取屬性,您可以將 spring.config.import 設定為 optional:configtree:/etc/config/myapp
使用點號表示法的檔名也能正確對應。例如,在上述範例中,/etc/config 中名為 myapp.username 的檔案將會在 Environment 中產生 myapp.username 屬性。
組態樹值可以繫結到字串 Stringbyte[] 類型,具體取決於預期的內容。

如果您有多個組態樹要從同一個父資料夾匯入,您可以使用萬用字元捷徑。任何以 /*/ 結尾的 configtree: 位置都會將所有直接子項匯入為組態樹。與非萬用字元匯入一樣,每個組態樹下的資料夾和檔案名稱會構成屬性名稱。

例如,假設有以下卷

etc/
  config/
    dbconfig/
      db/
        username
        password
    mqconfig/
      mq/
        username
        password

您可以使用 configtree:/etc/config/*/ 作為匯入位置

  • Properties

  • YAML

spring.config.import=optional:configtree:/etc/config/*/
spring:
  config:
    import: "optional:configtree:/etc/config/*/"

這將會新增 db.usernamedb.passwordmq.usernamemq.password 屬性。

使用萬用字元載入的目錄會依字母順序排序。如果您需要不同的順序,則應該將每個位置列為個別的匯入

組態樹也可以用於 Docker 密碼。當 Docker swarm 服務被授予存取密碼的權限時,密碼會被掛載到容器中。例如,如果名為 db.password 的密碼掛載在 /run/secrets/ 位置,您可以使用以下方式使 db.password 可用於 Spring 環境

  • Properties

  • YAML

spring.config.import=optional:configtree:/run/secrets/
spring:
  config:
    import: "optional:configtree:/run/secrets/"

屬性預留位置 (Property Placeholders)

application.propertiesapplication.yaml 中的值在使用時會透過現有的 Environment 進行篩選,因此您可以回溯參考先前定義的值(例如,來自系統屬性或環境變數)。標準的 ${name} 屬性預留位置語法可以在值內的任何位置使用。屬性預留位置也可以使用 : 來指定預設值,以分隔預設值和屬性名稱,例如 ${name:default}

以下範例顯示了使用和不使用預設值的預留位置

  • Properties

  • YAML

app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
app:
  name: "MyApp"
  description: "${app.name} is a Spring Boot application written by ${username:Unknown}"

假設 username 屬性尚未在其他地方設定,app.description 將會具有值 MyApp is a Spring Boot application written by Unknown

您應該始終在預留位置中使用屬性名稱的標準形式(僅使用小寫字母的 kebab-case)。這將允許 Spring Boot 使用與寬鬆繫結 @ConfigurationProperties 時相同的邏輯。

例如,${demo.item-price} 將會從 application.properties 檔案中選取 demo.item-pricedemo.itemPrice 形式,以及從系統環境中選取 DEMO_ITEMPRICE。如果您改用 ${demo.itemPrice},則不會考慮 demo.item-priceDEMO_ITEMPRICE

您也可以使用此技術來建立現有 Spring Boot 屬性的「簡短」變體。請參閱「操作指南」中的使用「簡短」命令列引數章節以取得詳細資訊。

使用多文件檔案 (Working With Multi-Document Files)

Spring Boot 允許您將單一實體檔案分割成多個邏輯文件,每個文件都會獨立新增。文件會依序處理,從上到下。後續的文件可以覆寫先前文件中定義的屬性。

對於 application.yaml 檔案,使用標準的 YAML 多文件語法。三個連續的連字號代表一個文件的結尾,以及下一個文件的開始。

例如,以下檔案有兩個邏輯文件

spring:
  application:
    name: "MyApp"
---
spring:
  application:
    name: "MyCloudApp"
  config:
    activate:
      on-cloud-platform: "kubernetes"

對於 application.properties 檔案,使用特殊的 #---!--- 註解來標記文件分割

spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
屬性檔案分隔符號不得有任何前導空格,且必須正好有三個連字號字元。分隔符號緊鄰的前後行不得為相同的註解前綴。
多文件屬性檔案通常與啟用屬性(例如 spring.config.activate.on-profile)結合使用。請參閱下一節以取得詳細資訊。
多文件屬性檔案無法使用 @PropertySource@TestPropertySource 註解載入。

啟用屬性 (Activation Properties)

有時,僅在滿足特定條件時才啟用給定的屬性集會很有用。例如,您可能有一些屬性僅在特定 Profile 啟用時才相關。

您可以使用 spring.config.activate.* 有條件地啟用屬性文件。

以下啟用屬性可用

表 1. 啟用屬性
屬性 (Property) 注意 (Note)

on-profile

文件必須符合的 Profile 表達式才能啟用。

on-cloud-platform

偵測到文件必須符合的 CloudPlatform 才能啟用。

例如,以下指定第二個文件僅在 Kubernetes 上執行時啟用,並且僅在啟用 “prod” 或 “staging” Profile 時啟用

  • Properties

  • YAML

myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

加密屬性 (Encrypting Properties)

Spring Boot 未提供任何內建的屬性值加密支援,但是,它確實提供了修改 Spring Environment 中包含的值所需的掛鉤點。EnvironmentPostProcessor 介面允許您在應用程式啟動之前操作 Environment。請參閱在應用程式啟動之前自訂 Environment 或 ApplicationContext 以取得詳細資訊。

如果您需要安全的方式來儲存憑證和密碼,Spring Cloud Vault 專案提供在 HashiCorp Vault 中儲存外部化組態的支援。

使用 YAML (Working With YAML)

YAML 是 JSON 的超集合,因此,它是指定階層式組態資料的便捷格式。只要您的類別路徑上有 SnakeYAML 程式庫,SpringApplication 類別就會自動支援 YAML 作為屬性的替代方案。

如果您使用 starters,spring-boot-starter 會自動提供 SnakeYAML。

將 YAML 對應到屬性 (Mapping YAML to Properties)

YAML 文件需要從其階層式格式轉換為可用於 Spring Environment 的扁平結構。例如,考慮以下 YAML 文件

environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"

為了從 Environment 存取這些屬性,它們會被扁平化如下

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

同樣地,YAML 列表也需要扁平化。它們表示為帶有 [index] 取值符號的屬性鍵。例如,考慮以下 YAML

 my:
  servers:
  - "dev.example.com"
  - "another.example.com"

先前的範例將會轉換為這些屬性

my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用 [index] 表示法的屬性可以使用 Spring Boot 的 Binder 類別繫結到 Java ListSet 物件。如需更多詳細資訊,請參閱下方的類型安全組態屬性章節。
YAML 檔案無法使用 @PropertySource@TestPropertySource 註解載入。因此,如果您需要以這種方式載入值,則需要使用屬性檔案。

直接載入 YAML (Directly Loading YAML)

Spring Framework 提供了兩個方便的類別,可用於載入 YAML 文件。YamlPropertiesFactoryBean 將 YAML 載入為 Properties,而 YamlMapFactoryBean 將 YAML 載入為 Map

如果您想將 YAML 載入為 Spring PropertySource,您也可以使用 YamlPropertySourceLoader 類別。

設定隨機值 (Configuring Random Values)

RandomValuePropertySource 對於注入隨機值(例如,注入到密碼或測試案例中)很有用。它可以產生整數、長整數、uuid 或字串,如下列範例所示

  • Properties

  • YAML

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
my:
  secret: "${random.value}"
  number: "${random.int}"
  bignumber: "${random.long}"
  uuid: "${random.uuid}"
  number-less-than-ten: "${random.int(10)}"
  number-in-range: "${random.int[1024,65536]}"

random.int* 語法為 OPEN value (,max) CLOSE,其中 OPEN,CLOSE 是任何字元,而 value,max 是整數。如果提供了 max,則 value 是最小值,而 max 是最大值(不包含)。

設定系統環境屬性 (Configuring System Environment Properties)

Spring Boot 支援為環境屬性設定前綴。如果系統環境由多個具有不同組態需求的 Spring Boot 應用程式共用,這會很有用。系統環境屬性的前綴可以直接在 SpringApplication 上設定。

例如,如果您將前綴設定為 input,則系統環境中諸如 remote.timeout 之類的屬性也會解析為 input.remote.timeout

類型安全組態屬性 (Type-safe Configuration Properties)

使用 @Value("${property}") 註解來注入組態屬性有時可能很麻煩,尤其是當您處理多個屬性或您的資料本質上是階層式時。Spring Boot 提供了一種替代方法來處理屬性,該方法允許強型別 Bean 管理和驗證應用程式的組態。

JavaBean 屬性繫結 (JavaBean Properties Binding)

可以繫結宣告標準 JavaBean 屬性的 Bean,如下列範例所示

  • Java

  • Kotlin

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my.service")
public class MyProperties {

	private boolean enabled;

	private InetAddress remoteAddress;

	private final Security security = new Security();

	// getters / setters...

	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		private String username;

		private String password;

		private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

		// getters / setters...

		public String getUsername() {
			return this.username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			this.password = password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

		public void setRoles(List<String> roles) {
			this.roles = roles;
		}

	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties {

	var isEnabled = false

	var remoteAddress: InetAddress? = null

	val security = Security()

	class Security {

		var username: String? = null

		var password: String? = null

		var roles: List<String> = ArrayList(setOf("USER"))

	}

}

先前的 POJO 定義了以下屬性

  • my.service.enabled,預設值為 false

  • my.service.remote-address,其類型可以從 String 強制轉換。

  • my.service.security.username,具有巢狀的 "security" 物件,其名稱由屬性的名稱決定。特別是,此處完全未使用類型,並且可以是 SecurityProperties

  • my.service.security.password.

  • my.service.security.roles,具有預設為 USERString 集合。

對應到 Spring Boot 中可用的 @ConfigurationProperties 類別的屬性,這些屬性是透過屬性檔案、YAML 檔案、環境變數和其他機制設定的,是公開 API,但類別本身的存取器(getter/setter)並非旨在直接使用。

這種安排依賴於預設的空建構子,並且通常強制要求 getter 和 setter,因為繫結是透過標準 Java Beans 屬性描述器進行的,就像在 Spring MVC 中一樣。在以下情況下,可以省略 setter

  • 只要 Maps 已初始化,它們就需要 getter,但不一定需要 setter,因為它們可以由 binder 變更。

  • 集合和陣列可以透過索引(通常使用 YAML)或使用單一逗號分隔值(屬性)來存取。在後一種情況下,setter 是強制性的。我們建議始終為此類類型新增 setter。如果您初始化集合,請確保它不是不可變的(如先前的範例所示)。

  • 如果巢狀 POJO 屬性已初始化(例如先前範例中的 Security 欄位),則不需要 setter。如果您希望 binder 使用其預設建構子動態建立實例,則需要 setter。

有些人使用 Project Lombok 自動新增 getter 和 setter。請確保 Lombok 沒有為此類類型產生任何特定的建構子,因為容器會自動使用它來實例化物件。

最後,僅考慮標準 Java Bean 屬性,並且不支援靜態屬性的繫結。

建構子繫結 (Constructor Binding)

前一節中的範例可以用不可變的方式重寫,如下列範例所示

  • Java

  • Kotlin

import java.net.InetAddress;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConfigurationProperties("my.service")
public class MyProperties {

	// fields...

	private final boolean enabled;

	private final InetAddress remoteAddress;

	private final Security security;


	public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
		this.enabled = enabled;
		this.remoteAddress = remoteAddress;
		this.security = security;
	}

	// getters...

	public boolean isEnabled() {
		return this.enabled;
	}

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		// fields...

		private final String username;

		private final String password;

		private final List<String> roles;


		public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
			this.username = username;
			this.password = password;
			this.roles = roles;
		}

		// getters...

		public String getUsername() {
			return this.username;
		}

		public String getPassword() {
			return this.password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
		val security: Security) {

	class Security(val username: String, val password: String,
			@param:DefaultValue("USER") val roles: List<String>)

}

在此設定中,單一參數化建構子的存在意味著應使用建構子繫結。這表示 binder 將會找到一個建構子,其中包含您希望繫結的參數。如果您的類別有多個建構子,則可以使用 @ConstructorBinding 註解來指定要用於建構子繫結的建構子。若要選擇不使用具有單一參數化建構子的類別的建構子繫結,則建構子必須使用 @Autowired 註解或設為 private。建構子繫結可以與 records 一起使用。除非您的 record 有多個建構子,否則無需使用 @ConstructorBinding

建構子繫結類別的巢狀成員(例如上述範例中的 Security)也將透過其建構子繫結。

可以使用建構子參數和 record 元件上的 @DefaultValue 來指定預設值。轉換服務將用於強制轉換註解的 String 值為遺失屬性的目標類型。

參考先前的範例,如果沒有屬性繫結到 Security,則 MyProperties 實例將包含 securitynull 值。為了使其即使在沒有屬性繫結到它時也包含 Security 的非 null 實例(當使用 Kotlin 時,這將需要將 Securityusernamepassword 參數宣告為可為 null,因為它們沒有預設值),請使用空的 @DefaultValue 註解

  • Java

  • Kotlin

	public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
		this.enabled = enabled;
		this.remoteAddress = remoteAddress;
		this.security = security;
	}
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
		@DefaultValue val security: Security) {

	class Security(val username: String?, val password: String?,
			@param:DefaultValue("USER") val roles: List<String>)

}
若要使用建構子繫結,必須使用 @EnableConfigurationProperties 或組態屬性掃描啟用類別。您無法將建構子繫結與由常規 Spring 機制建立的 Bean 一起使用(例如 @Component Bean、使用 @Bean 方法建立的 Bean 或使用 @Import 載入的 Bean)
若要使用建構子繫結,類別必須使用 -parameters 編譯。如果您使用 Spring Boot 的 Gradle 外掛程式,或如果您使用 Maven 和 spring-boot-starter-parent,則會自動發生這種情況。
不建議將 java.util.Optional@ConfigurationProperties 一起使用,因為它主要用於作為傳回類型。因此,它不太適合組態屬性注入。為了與其他類型的屬性保持一致,如果您確實宣告了 Optional 屬性且它沒有值,則會繫結 null 而不是空的 Optional
若要在屬性名稱中使用保留關鍵字,例如 my.service.import,請在建構子參數上使用 @Name 註解。

啟用 @ConfigurationProperties 註解類型 (Enabling @ConfigurationProperties-annotated Types)

Spring Boot 提供了基礎架構來繫結 @ConfigurationProperties 類型並將它們註冊為 Bean。您可以逐個類別啟用組態屬性,或啟用組態屬性掃描,其運作方式與組件掃描類似。

有時,使用 @ConfigurationProperties 註解的類別可能不適合掃描,例如,如果您正在開發自己的自動組態,或者您想要有條件地啟用它們。在這些情況下,請使用 @EnableConfigurationProperties 註解指定要處理的類型列表。這可以在任何 @Configuration 類別上完成,如下列範例所示

  • Java

  • Kotlin

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some.properties")
public class SomeProperties {

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("some.properties")
class SomeProperties

若要使用組態屬性掃描,請將 @ConfigurationPropertiesScan 註解新增到您的應用程式。通常,它會新增到使用 @SpringBootApplication 註解的主要應用程式類別,但它可以新增到任何 @Configuration 類別。依預設,掃描將從宣告註解的類別的套件開始。如果您想定義要掃描的特定套件,您可以按照以下範例所示進行操作

  • Java

  • Kotlin

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan

@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication

當使用組態屬性掃描或透過 @EnableConfigurationProperties 註冊 @ConfigurationProperties Bean 時,Bean 具有傳統名稱:<prefix>-<fqn>,其中 <prefix> 是在 @ConfigurationProperties 註解中指定的環境鍵前綴,而 <fqn> 是 Bean 的完整限定名稱。如果註解未提供任何前綴,則僅使用 Bean 的完整限定名稱。

假設它在 com.example.app 套件中,上述 SomeProperties 範例的 Bean 名稱是 some.properties-com.example.app.SomeProperties

我們建議 @ConfigurationProperties 僅處理環境,特別是不從 Context 注入其他 Bean。對於特殊情況,可以使用 setter 注入或框架提供的任何 *Aware 介面(例如,如果您需要存取 Environment,則可以使用 EnvironmentAware)。如果您仍然想使用建構子注入其他 Bean,則組態屬性 Bean 必須使用 @Component 註解並使用基於 JavaBean 的屬性繫結。

使用 @ConfigurationProperties 註解類型 (Using @ConfigurationProperties-annotated Types)

這種組態樣式特別適用於 SpringApplication 外部 YAML 組態,如下列範例所示

my:
  service:
    remote-address: 192.168.1.1
    security:
      username: "admin"
      roles:
      - "USER"
      - "ADMIN"

若要使用 @ConfigurationProperties Bean,您可以像注入任何其他 Bean 一樣注入它們,如下列範例所示

  • Java

  • Kotlin

import org.springframework.stereotype.Service;

@Service
public class MyService {

	private final MyProperties properties;

	public MyService(MyProperties properties) {
		this.properties = properties;
	}

	public void openConnection() {
		Server server = new Server(this.properties.getRemoteAddress());
		server.start();
		// ...
	}

	// ...

}
import org.springframework.stereotype.Service

@Service
class MyService(val properties: MyProperties) {

	fun openConnection() {
		val server = Server(properties.remoteAddress)
		server.start()
		// ...
	}

	// ...

}
使用 @ConfigurationProperties 也可讓您產生中繼資料檔案,IDE 可以使用這些檔案為您自己的鍵提供自動完成功能。請參閱附錄以取得詳細資訊。

第三方組態 (Third-party Configuration)

除了使用 @ConfigurationProperties 註解類別之外,您也可以在公開的 @Bean 方法上使用它。當您想要將屬性繫結到不受您控制的第三方組件時,這樣做可能特別有用。

若要從 Environment 屬性組態 Bean,請將 @ConfigurationProperties 新增到其 Bean 註冊,如下列範例所示

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

	@Bean
	@ConfigurationProperties(prefix = "another")
	public AnotherComponent anotherComponent() {
		return new AnotherComponent();
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class ThirdPartyConfiguration {

	@Bean
	@ConfigurationProperties(prefix = "another")
	fun anotherComponent(): AnotherComponent = AnotherComponent()

}

任何使用 another 前綴定義的 JavaBean 屬性都會以與先前 SomeProperties 範例類似的方式對應到該 AnotherComponent Bean。

寬鬆繫結 (Relaxed Binding)

Spring Boot 對於將 Environment 屬性繫結到 @ConfigurationProperties Bean 使用了一些寬鬆的規則,因此 Environment 屬性名稱和 Bean 屬性名稱之間不需要完全匹配。這在常見的範例中很有用,包括以破折號分隔的環境屬性(例如,context-path 繫結到 contextPath)和首字母大寫的環境屬性(例如,PORT 繫結到 port)。

舉例來說,考慮以下 @ConfigurationProperties 類別

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {

	private String firstName;

	public String getFirstName() {
		return this.firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {

	var firstName: String? = null

}

使用先前的程式碼,可以使用以下所有屬性名稱

表 2. 寬鬆繫結
屬性 (Property) 注意 (Note)

my.main-project.person.first-name

Kebab case,建議用於 .properties 和 YAML 檔案。

my.main-project.person.firstName

標準 camel case 語法。

my.main-project.person.first_name

底線表示法,這是用於 .properties 和 YAML 檔案的替代格式。

MY_MAINPROJECT_PERSON_FIRSTNAME

大寫格式,建議在使用系統環境變數時使用。

註解的 prefix必須採用 kebab case 形式(小寫並以 - 分隔,例如 my.main-project.person)。
表 3. 每個屬性來源的寬鬆繫結規則
屬性來源 (Property Source) 簡單 (Simple) 列表 (List)

屬性檔案 (Properties Files)

Camel case、kebab case 或底線表示法

使用 [ ] 的標準列表語法或逗號分隔值

YAML 檔案 (YAML Files)

Camel case、kebab case 或底線表示法

標準 YAML 列表語法或逗號分隔值

環境變數 (Environment Variables)

以底線作為分隔符號的大寫格式(請參閱從環境變數繫結)。

以底線包圍的數值(請參閱從環境變數繫結

系統屬性 (System properties)

Camel case、kebab case 或底線表示法

使用 [ ] 的標準列表語法或逗號分隔值

我們建議盡可能將屬性儲存在小寫 kebab 格式中,例如 my.person.first-name=Rod

繫結 Maps (Binding Maps)

當繫結到 Map 屬性時,您可能需要使用特殊的方括號表示法,以便保留原始的 key 值。如果鍵未以 [] 包圍,則會移除任何非英數字元、-. 的字元。

例如,考慮將以下屬性繫結到 Map<String,String>

  • Properties

  • YAML

my.map[/key1]=value1
my.map[/key2]=value2
my.map./key3=value3
my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"
對於 YAML 檔案,方括號需要用引號括起來,以便正確解析鍵。

上面的屬性將會繫結到一個 Map,其中 /key1/key2key3 作為 Map 中的鍵。斜線已從 key3 中移除,因為它沒有以方括號包圍。

當繫結到純量值時,鍵中帶有 . 的鍵不需要以 [] 包圍。純量值包括列舉和 java.lang 套件中的所有類型,但 Object 除外。將 a.b=c 繫結到 Map<String, String> 將會保留鍵中的 .,並傳回一個具有條目 {"a.b"="c"} 的 Map。對於任何其他類型,如果您的 key 包含 .,則需要使用方括號表示法。例如,將 a.b=c 繫結到 Map<String, Object> 將會傳回一個具有條目 {"a"={"b"="c"}} 的 Map,而 [a.b]=c 將會傳回一個具有條目 {"a.b"="c"} 的 Map。

從環境變數繫結 (Binding From Environment Variables)

大多數作業系統對可用於環境變數的名稱施加嚴格的規則。例如,Linux shell 變數只能包含字母(azAZ)、數字(09)或底線字元(_)。依照慣例,Unix shell 變數的名稱也會使用大寫。

Spring Boot 的寬鬆繫結規則盡可能設計為與這些命名限制相容。

若要將標準形式的屬性名稱轉換為環境變數名稱,您可以遵循以下規則

  • 將點號 (.) 替換為底線 (_)。

  • 移除任何破折號 (-)。

  • 轉換為大寫。

例如,組態屬性 spring.main.log-startup-info 將會是一個名為 SPRING_MAIN_LOGSTARTUPINFO 的環境變數。

環境變數也可以在繫結到物件列表時使用。若要繫結到 List,元素編號應以變數名稱中的底線包圍。例如,組態屬性 my.service[0].other 將會使用名為 MY_SERVICE_0_OTHER 的環境變數。

從環境變數繫結的支援適用於 systemEnvironment 屬性來源以及任何名稱以 -systemEnvironment 結尾的其他屬性來源。

從環境變數繫結 Maps (Binding Maps From Environment Variables)

當 Spring Boot 將環境變數繫結到屬性類別時,它會在繫結之前將環境變數名稱轉換為小寫。大多數時候,此細節並不重要,除非繫結到 Map 屬性。

Map 中的鍵始終為小寫,如下列範例所示

  • Java

  • Kotlin

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my.props")
public class MyMapsProperties {

	private final Map<String, String> values = new HashMap<>();

	public Map<String, String> getValues() {
		return this.values;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "my.props")
class MyMapsProperties {

	val values: Map<String, String> = HashMap()

}

當設定 MY_PROPS_VALUES_KEY=value 時,values Map 包含一個 {"key"="value"} 條目。

僅環境變數名稱會轉換為小寫,而不是值。當設定 MY_PROPS_VALUES_KEY=VALUE 時,values Map 包含一個 {"key"="VALUE"} 條目。

快取 (Caching)

寬鬆繫結使用快取來提高效能。依預設,此快取僅適用於不可變的屬性來源。若要自訂此行為,例如為可變的屬性來源啟用快取,請使用 ConfigurationPropertyCaching

合併複雜類型 (Merging Complex Types)

當在多個位置設定列表時,覆寫會透過替換整個列表來運作。

例如,假設一個 MyPojo 物件,其 namedescription 屬性依預設為 null。以下範例從 MyProperties 公開 MyPojo 物件的列表

  • Java

  • Kotlin

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

	private final List<MyPojo> list = new ArrayList<>();

	public List<MyPojo> getList() {
		return this.list;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

	val list: List<MyPojo> = ArrayList()

}

考慮以下組態

  • Properties

  • YAML

my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
my:
  list:
  - name: "my name"
    description: "my description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

如果 dev Profile 未啟用,則 MyProperties.list 包含一個 MyPojo 條目,如先前定義的。但是,如果啟用了 dev Profile,則 list 仍然僅包含一個條目(名稱為 my another name,描述為 null)。此組態不會將第二個 MyPojo 實例新增到列表中,也不會合併項目。

List 在多個 Profile 中指定時,將使用優先順序最高的 Profile(且僅使用該 Profile)。考慮以下範例

  • Properties

  • YAML

my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
my:
  list:
  - name: "my name"
    description: "my description"
  - name: "another name"
    description: "another description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

在先前的範例中,如果 dev Profile 已啟用,則 MyProperties.list 包含一個 MyPojo 條目(名稱為 my another name,描述為 null)。對於 YAML,逗號分隔列表和 YAML 列表都可以用於完全覆寫列表的內容。

對於 Map 屬性,您可以使用從多個來源提取的屬性值進行繫結。但是,對於多個來源中的相同屬性,將使用優先順序最高的來源。以下範例從 MyProperties 公開 Map<String, MyPojo>

  • Java

  • Kotlin

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

	private final Map<String, MyPojo> map = new LinkedHashMap<>();

	public Map<String, MyPojo> getMap() {
		return this.map;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

	val map: Map<String, MyPojo> = LinkedHashMap()

}

考慮以下組態

  • Properties

  • YAML

my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
my:
  map:
    key1:
      name: "my name 1"
      description: "my description 1"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  map:
    key1:
      name: "dev name 1"
    key2:
      name: "dev name 2"
      description: "dev description 2"

如果 dev Profile 未啟用,則 MyProperties.map 包含一個鍵為 key1 的條目(名稱為 my name 1,描述為 my description 1)。但是,如果啟用了 dev Profile,則 map 包含兩個條目,鍵分別為 key1(名稱為 dev name 1,描述為 my description 1)和 key2(名稱為 dev name 2,描述為 dev description 2)。

先前的合併規則適用於來自所有屬性來源的屬性,而不僅僅是檔案。

屬性轉換 (Properties Conversion)

當 Spring Boot 繫結到 @ConfigurationProperties Bean 時,它會嘗試將外部應用程式屬性強制轉換為正確的類型。如果您需要自訂類型轉換,您可以提供 ConversionService Bean(Bean 名稱為 conversionService)或自訂屬性編輯器(透過 CustomEditorConfigurer Bean)或自訂 Converters(Bean 定義使用 @ConfigurationPropertiesBinding 註解)。

由於此 Bean 在應用程式生命週期的早期就被請求,因此請確保限制您的 ConversionService 正在使用的依賴項。通常,您所需的任何依賴項可能在建立時尚未完全初始化。如果您的自訂 ConversionService 不是組態鍵強制轉換所必需的,並且僅依賴於使用 @ConfigurationPropertiesBinding 限定詞的自訂轉換器,您可能需要重新命名您的自訂 ConversionService

轉換持續時間 (Converting Durations)

Spring Boot 專門支援表示持續時間。如果您公開 java.time.Duration 屬性,則應用程式屬性中可以使用以下格式

  • 常規 long 表示法(使用毫秒作為預設單位,除非已指定 @DurationUnit

  • java.time.Duration 使用的標準 ISO-8601 格式

  • 更易讀的格式,其中值和單位耦合在一起(10s 表示 10 秒)

考慮以下範例

  • Java

  • Kotlin

import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

	@DurationUnit(ChronoUnit.SECONDS)
	private Duration sessionTimeout = Duration.ofSeconds(30);

	private Duration readTimeout = Duration.ofMillis(1000);

	// getters / setters...

	public Duration getSessionTimeout() {
		return this.sessionTimeout;
	}

	public void setSessionTimeout(Duration sessionTimeout) {
		this.sessionTimeout = sessionTimeout;
	}

	public Duration getReadTimeout() {
		return this.readTimeout;
	}

	public void setReadTimeout(Duration readTimeout) {
		this.readTimeout = readTimeout;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties {

	@DurationUnit(ChronoUnit.SECONDS)
	var sessionTimeout = Duration.ofSeconds(30)

	var readTimeout = Duration.ofMillis(1000)

}

若要指定 30 秒的會話逾時,30PT30S30s 都是等效的。500 毫秒的讀取逾時可以使用以下任何一種形式指定:500PT0.5S500ms

您也可以使用任何支援的單位。這些是

  • ns 代表奈秒 (nanoseconds)

  • us 代表微秒 (microseconds)

  • ms 代表毫秒 (milliseconds)

  • s 代表秒 (seconds)

  • m 代表分鐘 (minutes)

  • h 代表小時 (hours)

  • d 代表天 (days)

預設單位為毫秒,可以使用 @DurationUnit 覆寫,如上面的範例所示。

如果您偏好使用建構子繫結,則可以公開相同的屬性,如下列範例所示

  • Java

  • Kotlin

import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

	// fields...
	private final Duration sessionTimeout;

	private final Duration readTimeout;

	public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
			@DefaultValue("1000ms") Duration readTimeout) {
		this.sessionTimeout = sessionTimeout;
		this.readTimeout = readTimeout;
	}

	// getters...

	public Duration getSessionTimeout() {
		return this.sessionTimeout;
	}

	public Duration getReadTimeout() {
		return this.readTimeout;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties(@param:DurationUnit(ChronoUnit.SECONDS) @param:DefaultValue("30s") val sessionTimeout: Duration,
		@param:DefaultValue("1000ms") val readTimeout: Duration)
如果您要升級 Long 屬性,請確保定義單位(使用 @DurationUnit),如果它不是毫秒。這樣做可以提供透明的升級路徑,同時支援更豐富的格式。

轉換期間 (Converting Periods)

除了持續時間之外,Spring Boot 也可以使用 java.time.Period 類型。以下格式可用於應用程式屬性中

  • 常規 int 表示法(使用天作為預設單位,除非已指定 @PeriodUnit

  • java.time.Period 使用的標準 ISO-8601 格式

  • 更簡單的格式,其中值和單位對耦合在一起(1y3d 表示 1 年和 3 天)

簡單格式支援以下單位

  • y 代表年 (years)

  • m 代表月份

  • w 代表週

  • d 代表天 (days)

java.time.Period 類型實際上從未儲存週數,它只是一個代表「7 天」的捷徑。

轉換資料大小

Spring Framework 具有 DataSize 值類型,用於表示以位元組為單位的大小。如果您公開 DataSize 屬性,則應用程式屬性中可以使用以下格式

  • 常規 long 表示法 (使用位元組作為預設單位,除非已指定 @DataSizeUnit)

  • 更易讀的格式,其中值和單位結合在一起 (10MB 代表 10 兆位元組)

考慮以下範例

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

	@DataSizeUnit(DataUnit.MEGABYTES)
	private DataSize bufferSize = DataSize.ofMegabytes(2);

	private DataSize sizeThreshold = DataSize.ofBytes(512);

	// getters/setters...

	public DataSize getBufferSize() {
		return this.bufferSize;
	}

	public void setBufferSize(DataSize bufferSize) {
		this.bufferSize = bufferSize;
	}

	public DataSize getSizeThreshold() {
		return this.sizeThreshold;
	}

	public void setSizeThreshold(DataSize sizeThreshold) {
		this.sizeThreshold = sizeThreshold;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties {

	@DataSizeUnit(DataUnit.MEGABYTES)
	var bufferSize = DataSize.ofMegabytes(2)

	var sizeThreshold = DataSize.ofBytes(512)

}

若要指定 10 兆位元組的緩衝區大小,1010MB 是等效的。256 位元組的大小閾值可以指定為 256256B

您也可以使用任何支援的單位。這些是

  • B 代表位元組

  • KB 代表千位元組

  • MB 代表兆位元組

  • GB 代表吉位元組

  • TB 代表兆兆位元組

預設單位是位元組,可以使用 @DataSizeUnit 覆寫,如上述範例所示。

如果您偏好使用建構子繫結,則可以公開相同的屬性,如下列範例所示

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

	// fields...
	private final DataSize bufferSize;

	private final DataSize sizeThreshold;

	public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
			@DefaultValue("512B") DataSize sizeThreshold) {
		this.bufferSize = bufferSize;
		this.sizeThreshold = sizeThreshold;
	}

	// getters...

	public DataSize getBufferSize() {
		return this.bufferSize;
	}

	public DataSize getSizeThreshold() {
		return this.sizeThreshold;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties(@param:DataSizeUnit(DataUnit.MEGABYTES) @param:DefaultValue("2MB") val bufferSize: DataSize,
		@param:DefaultValue("512B") val sizeThreshold: DataSize)
如果您要升級 Long 屬性,請確保定義單位 (使用 @DataSizeUnit),如果它不是位元組。這樣做可以在支援更豐富格式的同時,提供透明的升級路徑。

@ConfigurationProperties 驗證

每當 @ConfigurationProperties 類別使用 Spring 的 @Validated 註解進行註釋時,Spring Boot 都會嘗試驗證它們。您可以直接在您的配置類別上使用 JSR-303 jakarta.validation 限制註解。為此,請確保您的類別路徑上有符合規範的 JSR-303 實作,然後將限制註解新增到您的欄位,如下列範例所示

  • Java

  • Kotlin

import java.net.InetAddress;

import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

	@NotNull
	private InetAddress remoteAddress;

	// getters/setters...

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

}
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

	var remoteAddress: @NotNull InetAddress? = null

}
您也可以透過使用 @Validated 註解註釋建立配置屬性的 @Bean 方法來觸發驗證。

為了確保始終為巢狀屬性觸發驗證,即使在未找到任何屬性的情況下,也必須使用 @Valid 註解關聯的欄位。以下範例建立在先前的 MyProperties 範例之上

  • Java

  • Kotlin

import java.net.InetAddress;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

	@NotNull
	private InetAddress remoteAddress;

	@Valid
	private final Security security = new Security();

	// getters/setters...

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		@NotEmpty
		private String username;

		// getters/setters...

		public String getUsername() {
			return this.username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

	}

}
import jakarta.validation.Valid
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

	var remoteAddress: @NotNull InetAddress? = null

	@Valid
	val security = Security()

	class Security {

		@NotEmpty
		var username: String? = null

	}

}

您還可以透過建立名為 configurationPropertiesValidator 的 Bean 定義來新增自訂 Spring Validator@Bean 方法應宣告為 static。配置屬性驗證器在應用程式生命週期的早期建立,將 @Bean 方法宣告為 static 可以讓 Bean 在無需實例化 @Configuration 類別的情況下建立。這樣做可以避免任何可能由早期實例化引起的問題。

spring-boot-actuator 模組包含一個端點,用於公開所有 @ConfigurationProperties Bean。將您的網頁瀏覽器指向 /actuator/configprops 或使用等效的 JMX 端點。有關詳細資訊,請參閱「生產就緒功能」章節。

@ConfigurationProperties 與 @Value

@Value 註解是核心容器功能,它不提供與型別安全配置屬性相同的功能。下表總結了 @ConfigurationProperties@Value 支援的功能

功能 @ConfigurationProperties @Value

寬鬆綁定

有限 (請參閱下方註解)

中繼資料支援

SpEL 評估

如果您確實想使用 @Value,我們建議您使用其規範形式 (kebab-case,僅使用小寫字母) 來參考屬性名稱。這將允許 Spring Boot 使用與寬鬆綁定 @ConfigurationProperties 時相同的邏輯。

例如,@Value("${demo.item-price}") 將從 application.properties 檔案中選取 demo.item-pricedemo.itemPrice 形式,以及從系統環境中選取 DEMO_ITEMPRICE。如果您改用 @Value("${demo.itemPrice}"),則不會考慮 demo.item-priceDEMO_ITEMPRICE

如果您為自己的元件定義一組配置金鑰,我們建議您將它們分組在以 @ConfigurationProperties 註釋的 POJO 中。這樣做將為您提供結構化、型別安全的物件,您可以將其注入到您自己的 Bean 中。

在剖析這些檔案並填入環境時,不會處理來自應用程式屬性檔案SpEL 運算式。但是,可以在 @Value 中編寫 SpEL 運算式。如果應用程式屬性檔案中屬性的值是 SpEL 運算式,則在透過 @Value 使用時將會評估它。