外部化配置

Spring Boot 允許你將配置外部化,以便在不同環境中使用相同的應用程式程式碼。你可以使用多種外部配置源,包括 Java properties 檔案、YAML 檔案、環境變數和命令列引數。

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

Spring Boot 使用了一種非常特定的 PropertySource 順序,旨在允許合理地覆蓋值。後加載的屬性源可以覆蓋先載入的屬性源中定義的值。屬性源按以下順序考慮:

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

  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 處於活動狀態時,位於 $HOME/.config/spring-boot 目錄中的Devtools 全域性設定屬性

配置資料檔案按以下順序考慮:

  1. 打包在你的 jar 內部的應用程式屬性application.properties 及其 YAML 變體)。

  2. 打包在你的 jar 內部的特定 Profile 的應用程式屬性application-{profile}.properties 及其 YAML 變體)。

  3. 打包在你的 jar 外部的應用程式屬性application.properties 及其 YAML 變體)。

  4. 打包在你的 jar 外部的特定 Profile 的應用程式屬性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 中。

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

$ 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/ 子目錄的直接子目錄

列表按優先順序排序(較低項的值會覆蓋較早項的值)。從載入的檔案中獲取的文件作為 PropertySource 例項新增到 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 中指定的檔案會直接匯入。

目錄和檔案位置值都會被擴充套件,以檢查特定 profile 的檔案。例如,如果你的 spring.config.locationclasspath:myconfig.properties,你也會發現相應的 classpath:myconfig-<profile>.properties 檔案被載入。

在大多數情況下,你新增的每個 spring.config.location 項都會引用單個檔案或目錄。位置按照定義的順序處理,後面的位置可以覆蓋前面位置的值。

如果你有一個複雜的位置設定,並且使用了特定 profile 的配置檔案,你可能需要提供進一步的提示,以便 Spring Boot 知道它們應該如何分組。位置組是同一級別考慮的位置集合。例如,你可能希望先將所有類路徑位置分組,然後將所有外部位置分組。位置組內的項應使用 ; 分隔。詳情請參閱特定 Profile 檔案部分中的示例。

使用 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 檔案缺失,你的應用程式也能啟動。

如果你想忽略所有 ConfigDataLocationNotFoundException 錯誤並始終繼續啟動應用程式,可以使用 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: 位置中使用萬用字元。

特定 Profile 檔案

除了 application 屬性檔案外,Spring Boot 還會嘗試使用命名約定 application-{profile} 載入特定 profile 的檔案。例如,如果你的應用程式激活了名為 prod 的 profile 並使用 YAML 檔案,則會同時考慮 application.yamlapplication-prod.yaml

特定 profile 的屬性會從與標準 application.properties 相同的位置載入,特定 profile 的檔案總是會覆蓋非特定檔案。如果指定了多個 profile,則應用後一個優先的策略。例如,如果 spring.profiles.active 屬性指定了 prod,live 這兩個 profile,則 application-prod.properties 中的值可以被 application-live.properties 中的值覆蓋。

後一個優先的策略適用於位置組級別。spring.config.location 的值為 classpath:/cfg/,classpath:/ext/ 與值為 classpath:/cfg/;classpath:/ext/ 的覆蓋規則不同。

例如,沿用上面 prod,live 的例子,我們可能有以下檔案:

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

當我們設定 spring.config.locationclasspath:/cfg/,classpath:/ext/ 時,我們會先處理所有 /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 有一組預設 profile(預設情況下是 [default]),如果在沒有設定活動 profile 的情況下使用它們。換句話說,如果沒有顯式啟用任何 profile,則會考慮來自 application-default 的屬性。

屬性檔案只會載入一次。如果你已經直接匯入了特定 profile 的屬性檔案,則不會再次匯入。

匯入額外資料

應用程式屬性可以使用 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 鍵下指定多個位置。位置將按照定義的順序處理,後匯入的將優先。

在適當的時候,也會考慮特定 Profile 的變體進行匯入。上面的例子會匯入 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. 多個檔案寫入目錄樹,檔名成為“鍵”,檔案內容成為“值”。

對於第一種情況,您可以使用 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.username, db.password, mq.usernamemq.password 屬性。

使用萬用字元載入的目錄按字母順序排序。如果您需要不同的順序,則應將每個位置單獨列為一個匯入項

配置樹也可用於 Docker secrets。當 Docker swarm 服務被授予訪問 secret 的許可權時,該 secret 將被掛載到容器中。例如,如果一個名為 db.password 的 secret 掛載在 /run/secrets/ 位置,您可以使用以下方式將 db.password 提供給 Spring Environment

  • Properties

  • YAML

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

屬性佔位符

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 屬性的“短”變體。有關詳細資訊,請參閱“How-to Guides”中的使用“短”命令列引數一節。

使用多文件檔案

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 註解載入多文件屬性檔案。

啟用屬性

有時,在滿足特定條件時才啟用給定屬性集會很有用。例如,您可能有一些屬性僅在特定 profile 處於活動狀態時才相關。

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

可用的啟用屬性如下

表 1. 啟用屬性
屬性 注意

on-profile

文件要處於活動狀態必須匹配的 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"

加密屬性

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

如果您需要一種安全的方式來儲存憑據和密碼,Spring Cloud Vault 專案提供了將外部化配置儲存在 HashiCorp Vault 中的支援。

使用 YAML

YAML 是 JSON 的超集,因此是一種方便的格式,用於指定分層配置資料。只要您的 classpath 中包含 SnakeYAML 庫,SpringApplication 類會自動支援 YAML 作為屬性檔案的替代方案。

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

將 YAML 對映到屬性

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 物件。有關更多詳細資訊,請參閱下面的型別安全的配置屬性一節。
不能使用 @PropertySource@TestPropertySource 註解載入 YAML 檔案。因此,如果您需要透過這種方式載入值,則需要使用 properties 檔案。

直接載入 YAML

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

如果您希望將 YAML 載入為 Spring PropertySource,您還可以使用 YamlPropertySourceLoader 類。

配置隨機值

RandomValuePropertySource 對於注入隨機值(例如,用於 secret 或測試用例)非常有用。它可以生成整數、長整數、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 是最大值(不包含)。

配置系統環境變數

Spring Boot 支援為環境變數設定字首。如果系統環境被具有不同配置要求的多個 Spring Boot 應用程式共享,這將非常有用。系統環境變數的字首可以直接在 SpringApplication 上設定。

例如,如果將字首設定為 input,則像 remote.timeout 這樣的屬性也將在系統環境中解析為 input.remote.timeout

型別安全的配置屬性

使用 @Value("${property}") 註解注入配置屬性有時可能很麻煩,特別是當您處理多個屬性或資料具有分層性質時。Spring Boot 提供了一種處理屬性的替代方法,允許強型別 bean 控制和驗證應用程式的配置。

另請參閱 @Value 與型別安全的配置屬性之間的區別

JavaBean 屬性繫結

可以繫結宣告標準 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,包含一個 String 集合,預設值為 USER

要在屬性名稱中使用保留關鍵字,例如 my.service.import,請在屬性的欄位上使用 @Name 註解。
對映到 Spring Boot 中提供的 @ConfigurationProperties 類的屬性(透過 properties 檔案、YAML 檔案、環境變數和其他機制配置)是公共 API,但類本身的訪問器(getter/setter)並非 intended 直接使用。

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

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

  • 集合和陣列可以透過索引(通常與 YAML 一起使用)或使用單個逗號分隔的值(properties 檔案)進行訪問。在後一種情況下,setter 是強制性的。我們建議始終為這些型別新增 setter。如果您初始化一個集合,請確保它不是不可變的(如前面的示例)。

  • 如果巢狀的 POJO 屬性已初始化(如前面示例中的 Security 欄位),則不需要 setter。如果您希望 binder 使用其預設建構函式即時建立例項,則需要 setter。

有些人使用 Project Lombok 自動新增 getter 和 setter。請確保 Lombok 不為此類型別生成任何特定建構函式,因為它由容器自動用於例項化物件。

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

建構函式繫結

上一節的示例可以用不可變的方式重寫,如以下示例所示

  • 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 值。即使沒有屬性繫結到它(使用 Kotlin 時,這需要將 Securityusernamepassword 引數宣告為可空,因為它們沒有預設值),若要使其包含非 null 的 Security 例項,請使用空的 @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,這將自動發生。
不建議將 Optional@ConfigurationProperties 一起使用,因為它主要 intended 用作返回型別。因此,它不太適合配置屬性注入。為了與其他型別屬性保持一致,如果您宣告一個 Optional 屬性但它沒有值,將繫結 null 而不是空的 Optional
要在屬性名稱中使用保留關鍵字,例如 my.service.import,請在建構函式引數上使用 @Name 註解。

啟用 @ConfigurationProperties 註解的型別

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 僅處理環境,特別是不要從上下文中注入其他 bean。對於特殊情況,可以使用 setter 注入或框架提供的任何 *Aware 介面(如果您需要訪問 Environment,例如 EnvironmentAware)。如果您仍然希望使用建構函式注入其他 bean,則配置屬性 bean 必須使用 @Component 註解並使用基於 JavaBean 的屬性繫結。

使用 @ConfigurationProperties 註解的型別

這種配置方式與 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 可以使用這些檔案為您的自定義鍵提供自動補全。有關詳細資訊,請參閱附錄

第三方配置

除了使用 @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。

寬鬆繫結

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. 寬鬆繫結
屬性 注意

my.main-project.person.first-name

Kebab 命名法,推薦用於 .properties 和 YAML 檔案中。

my.main-project.person.firstName

標準的駝峰命名法語法。

my.main-project.person.first_name

下劃線表示法,是 .properties 和 YAML 檔案中使用的另一種格式。

MY_MAINPROJECT_PERSON_FIRSTNAME

大寫格式,推薦在系統環境變數中使用。

註解的 prefix必須是 kebab 命名法(小寫並用 - 分隔,例如 my.main-project.person)。
表 3. 按屬性源區分的寬鬆繫結規則
屬性源 簡單型別 列表

Properties 檔案

駝峰命名法、kebab 命名法或下劃線表示法

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

YAML 檔案

駝峰命名法、kebab 命名法或下劃線表示法

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

環境變數

使用下劃線作為分隔符的大寫格式(參見 從環境變數繫結)。

被下劃線包圍的數字值(參見 從環境變數繫結

系統屬性

駝峰命名法、kebab 命名法或下劃線表示法

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

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

繫結 Map

當繫結到 Map 屬性時,您可能需要使用特殊的括號表示法,以便保留原始的 key 值。如果 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 檔案,括號需要用引號括起來,以便正確解析 key。

上述屬性將繫結到一個 Map,其 key 為 /key1/key2key3key3 中的斜槓已被移除,因為它沒有被方括號包圍。

當繫結到標量值時,包含 . 的 key 不需要被 [] 包圍。標量值包括列舉以及 java.lang 包中的所有型別(除了 Object)。將 a.b=c 繫結到 Map<String, String> 將保留 key 中的 .,並返回一個包含條目 {"a.b"="c"} 的 Map。對於任何其他型別,如果您的 key 包含 .,則需要使用括號表示法。例如,將 a.b=c 繫結到 Map<String, Object> 將返回一個包含條目 {"a"={"b"="c"}} 的 Map,而 [a.b]=c 將返回一個包含條目 {"a.b"="c"} 的 Map。

從環境變數繫結

大多數作業系統對環境變數的名稱有嚴格的規則。例如,Linux shell 變數只能包含字母(azAZ)、數字(09)或下劃線字元(_)。按照慣例,Unix shell 變數的名稱也通常採用大寫。

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

要將規範形式(canonical-form)的屬性名稱轉換為環境變數名稱,您可以遵循以下規則

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

  • 移除所有連字元 (-)。

  • 轉換為大寫。

例如,配置屬性 spring.main.log-startup-info 將對應名為 SPRING_MAIN_LOGSTARTUPINFO 的環境變數。

環境變數也可用於繫結物件列表。要繫結到 List,變數名稱中的元素索引應被下劃線包圍。

例如,配置屬性 my.service[0].other 將使用名為 MY_SERVICE_0_OTHER 的環境變數。

從環境變數繫結的支援應用於 systemEnvironment 屬性源以及任何名稱以 -systemEnvironment 結尾的附加屬性源。

從環境變數繫結 Map

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

Map 中的 key 始終是小寫的,如下例所示

  • 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"}

快取

寬鬆繫結使用快取來提高效能。預設情況下,此快取僅應用於不可變屬性源。要自定義此行為,例如為可變屬性源啟用快取,請使用 ConfigurationPropertyCaching

合併複雜型別

當列表在多個地方配置時,覆蓋的工作方式是替換整個列表。

例如,假設一個 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 中指定時,只使用優先順序最高的那一個。考慮以下示例

  • 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 包含一個 key 為 key1 的條目(名稱為 my name 1,描述為 my description 1)。但是,如果 dev profile 啟用,map 包含兩個條目,key 分別為 key1(名稱為 dev name 1,描述為 my description 1)和 key2(名稱為 dev name 2,描述為 dev description 2)。

上述合併規則適用於所有屬性源的屬性,而不僅僅是檔案。

屬性轉換

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

由於此 bean 在應用生命週期的早期就被請求,請確保限制您的 ConversionService 所使用的依賴項。通常,您需要的任何依賴項在建立時可能尚未完全初始化。如果您的自定義 ConversionService 不是配置 key 強制轉換所必需的,並且只依賴於使用 @ConfigurationPropertiesBinding 限定的自定義轉換器,則您可能需要重新命名它。

轉換 Duration

Spring Boot 為表示持續時間(duration)提供了專門的支援。如果您暴露 Duration 屬性,應用屬性中可使用以下格式

  • 常規的 long 表示法(除非指定了 @DurationUnit,否則預設單位為毫秒)

  • 標準 ISO-8601 格式,Duration 使用

  • 更易讀的格式,其中值和單位結合(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 都等效。500ms 的讀取超時可以用以下任一形式指定:500PT0.5S500ms

您也可以使用任何支援的單位。它們是

  • ns 表示納秒

  • us 表示微秒

  • ms 表示毫秒

  • s 表示秒

  • m 表示分鐘

  • h 表示小時

  • d 表示天

預設單位是毫秒,可以使用 @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)。這樣做可以提供一個透明的升級路徑,同時支援更豐富的格式。

轉換 Period

除了持續時間(duration),Spring Boot 還可以使用 Period 型別。應用屬性中可使用以下格式

  • 常規的 int 表示法(除非指定了 @PeriodUnit,否則預設單位為天)

  • 標準 ISO-8601 格式,Period 使用

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

簡單格式支援以下單位

  • y 表示年

  • m 表示月

  • w 表示周

  • d 表示天

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)。這樣做可以提供一個透明的升級路徑,同時支援更豐富的格式。

轉換 Base64 資料

Spring Boot 支援解析 Base64 編碼的二進位制資料。如果您暴露一個 Resource 屬性,可以將 Base64 編碼文字作為值提供,並加上 base64: 字首,如下例所示

  • Properties

  • YAML

my.property=base64:SGVsbG8gV29ybGQ=
my:
  property: base64:SGVsbG8gV29ybGQ=
Resource 屬性也可用於提供資源的路徑,使其更加靈活。

@ConfigurationProperties 驗證

每當 @ConfigurationProperties 類使用 Spring 的 @Validated 註解時,Spring Boot 會嘗試對其進行驗證。您可以直接在配置類上使用 JSR-303 jakarta.validation 約束註解。為此,請確保 classpath 中存在相容的 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

如果您為自己的元件定義了一組配置 key,我們建議您將它們組合到一個使用 @ConfigurationProperties 註解的 POJO 中。這樣做將為您提供結構化、型別安全的物件,您可以將其注入到自己的 bean 中。

應用屬性檔案中的 SpEL 表示式在解析這些檔案和填充環境時不會被處理。但是,可以在 @Value 中編寫 SpEL 表示式。如果來自應用屬性檔案的屬性值是 SpEL 表示式,則在使用 @Value 消費時會對其進行評估。