Spring Boot配置類的註解

公众号-JavaEdge發表於2024-08-11

Spring Boot 中,若某類只用 @ConfigurationProperties 註解,然後該類:

  • 沒有在掃描路徑下
  • 或沒用 @Component 等註解

就會導致無法被掃描為 bean,須在配置類用 @EnableConfigurationProperties 註解去指定這個類,才能使 @ConfigurationProperties 生效,並作為一個 bean 新增進 Spring 容器。

@EnableConfigurationProperties 不能單獨使用:

  • @EnableConfigurationProperties 和 @ConfigurationProperties組合使用
  • @EnableConfigurationProperties將@ConfigurationProperties所修飾的類新增到IoC容器

1 簡介

Spring Boot外部化配置和輕鬆訪問 Properties 檔案中定義的屬性。上文介紹了實現這一點的各種方法。

本文看Spring Boot 的 @ConfigurationProperties

2 專案設定

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
    <relativePath/>
</parent>

為驗證檔案中定義的屬性,還需要一個 JSR-380 實現,hibernate-validator 就是其中之一,它由 spring-boot-starter-validation 依賴提供。

把它也新增到 pom.xml 中:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

更多詳細資訊可參閱 Hibernate Validator 入門

3 示例屬性

官方建議將配置屬性隔離到單獨 POJO,如:

@Configuration
@ConfigurationProperties(prefix = "mail")
public class ConfigProperties {
    
    private String hostName;

 	  private int port;

    private String from;
}

透過 @Configuration,Spring 會在 Application Context 中建立一個 Spring Bean。

@ConfigurationProperties 適合具有相同字首的分層屬性。

Spring使用標準 Java Bean Setter,因此必須為每個屬性宣告Setter。

如不在 POJO 用 @Configuration,則需在 Spring Application main 類中新增 @EnableConfigurationProperties(ConfigProperties.class) 以將屬性繫結到 POJO:

@SpringBootApplication
@EnableConfigurationProperties(ConfigProperties.class)
public class EnableConfigurationDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(EnableConfigurationDemoApplication.class, args);
    }
}

Spring 會自動繫結屬性檔案中定義的、字首為 mail 且名稱與 ConfigProperties 類中某個欄位相同的任何屬性。

Spring 對繫結屬性使用寬鬆規則,以下各種變體都可繫結到屬性 hostName

mail.hostName
mail.hostname
mail.host_name
mail.host-name
mail.HOST_NAME

可用如下 properties 檔案設定所有欄位:

# 示例 properties
mail.hostname=host@mail.com
mail.port=9000
mail.from=mailer@mail.com

3.1、Spring Boot 2.2

Spring Boot 2.2 開始,Spring 透過 classpath 掃描查詢並註冊 @ConfigurationProperties 類。對 @ConfigurationProperties 掃描需透過新增 @ConfigurationPropertiesScan 來明確選擇。

因此,不必用 @Component(及 @Configuration 等其他元註解)註解這種類,甚至也不必用 @EnableConfigurationProperties

@ConfigurationProperties(prefix = "mail") 
@ConfigurationPropertiesScan 
public class ConfigProperties { 

    private String hostName; 
    private int port; 
    private String from; 
}

@SpringBootApplication 啟用的 classpath Scanner 找到了 ConfigProperties 類,儘管我們沒有用 @Component 對該類進行註解。

還可用@ConfigurationPropertiesScan掃描自定義位置的配置屬性類:

@SpringBootApplication
@ConfigurationPropertiesScan("com.baeldung.configurationproperties")
public class EnableConfigurationDemoApplication { 

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

如上,Spring 將只在 com.baeldung.properties 包中查詢配置屬性類。

4 巢狀屬性

可在 ListMapClass 中巢狀屬性。

建立一個新的 Credentials 類,用於一些巢狀屬性:

public class Credentials {
    private String authMethod;
    private String username;
    private String password;

    //Get、Set 方法
}

還需要更新 ConfigProperties 類,以便使用 ListMapCredentials 類:

public class ConfigProperties {

    private String hostname;
    private int port;
    private String from;
    private List<String> defaultRecipients;
    private Map<String, String> additionalHeaders;
    private Credentials credentials;
 
    // Get、Set 省略
}

下面的屬性檔案將設定所有欄位:

#Simple properties
mail.hostname=mailer@mail.com
mail.port=9000
mail.from=mailer@mail.com

#List properties
mail.defaultRecipients[0]=admin@mail.com
mail.defaultRecipients[1]=owner@mail.com

#Map Properties
mail.additionalHeaders.redelivery=true
mail.additionalHeaders.secure=true

#物件 properties
mail.credentials.username=john
mail.credentials.password=password
mail.credentials.authMethod=SHA1

5 @Bean 方法用 @ConfigurationProperties

當要將屬性繫結到無法控制的第三方元件時,特別有用。

public class Item {
    private String name;
    private int size;
}

看咋在 @Bean 方法中使用 @ConfigurationProperties 將外部化屬性繫結到 Item 例項:

@Configuration
public class ConfigProperties {

    @Bean
    @ConfigurationProperties(prefix = "item")
    public Item item() {
        return new Item();
    }
}

任何 item 字首的屬性都將對映到 Spring Context 管理的 Item 例項。

6 屬性校驗

@ConfigurationProperties 使用 JSR-380 格式對屬性進行驗證。

例如,把 hostName 屬性成為強制性屬性:

@NotBlank
private String hostName;

接下來,把 authMethod 屬性的長度設為 14 個字元:

@Length(max = 4, min = 1)
private String authMethod;

然後,port 屬性是 102565536

@Min(1025)
@Max(65536)
private int port;

最後,from 屬性必須與電子郵件地址格式相匹配:

@Pattern(regexp = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,6}$")
private String from;

這可以幫助減少程式碼中大量的 if - else 判斷條件,使程式碼看起來更簡潔明瞭。

如果其中任何一項驗證失敗,應用將無法啟動,並丟擲 IllegalStateException 異常。

Hibernate Validation 框架使用標準的 Java Bean Getter 和 Setter,因此必須為每個屬性宣告 Getter 和 Setter 方法。

7 屬性型別轉換

@ConfigurationProperties 支援多種型別的轉換,可將屬性繫結到相應的 Bean。

7.1 Duration

先來看看如何將屬性轉換為 Duration 物件。

這裡有兩個 Duration 型別的欄位:

@ConfigurationProperties(prefix = "conversion")
public class PropertyConversion {

    private Duration timeInDefaultUnit;
    private Duration timeInNano;
    ...
}

配置屬性如下:

conversion.timeInDefaultUnit=10
conversion.timeInNano=9ns

如上,欄位 timeInDefaultUnit 的值為 10 毫秒,而 timeInNano 的值為 9 納秒。

支援的單位有 nsusmssmhd,分別表示納秒、微秒、毫秒、秒、分、小時和天。

預設單位是毫秒,這意味著如果不在數值旁邊指定單位,Spring 就會將數值轉換為毫秒。

還可以使用 @DurationUnit 來覆蓋預設單位:

@DurationUnit(ChronoUnit.DAYS)
private Duration timeInDays;

對應的屬性如下:

conversion.timeInDays=2

7.2、DataSize

同樣,Spring Boot @ConfigurationProperties 也支援 DataSize 型別轉換。

新增三個 DataSize 型別的欄位:

private DataSize sizeInDefaultUnit;

private DataSize sizeInGB;

@DataSizeUnit(DataUnit.TERABYTES)
private DataSize sizeInTB;

相應的屬性如下:

conversion.sizeInDefaultUnit=300
conversion.sizeInGB=2GB
conversion.sizeInTB=4

如上,sizeInDefaultUnit 的值是 300 位元組,因為預設單位是位元組。

支援的單位有 BKBMBGBTB。還可以使用 @DataSizeUnit 覆蓋預設單位。

7.3、自定義 Converter

還可以新增自己的自定義 Converter,以支援將屬性轉換為特定的類型別。

新增一個簡單的 Employee 類:

public class Employee {
    private String name;
    private double salary;
}

然後,建立一個自定義 Converter 來轉換該屬性:

conversion.employee=john,2000

把它轉換為 Employee 型別:

private Employee employee;

需要實現 Converter 介面,然後使用 @ConfigurationPropertiesBinding 註解註冊自定義 Converter

@Component
@ConfigurationPropertiesBinding
public class EmployeeConverter implements Converter<String, Employee> {

    @Override
    public Employee convert(String from) {
        String[] data = from.split(",");
        return new Employee(data[0], Double.parseDouble(data[1]));
    }
}

8 不可變的 @ConfigurationProperties 繫結

從 Spring Boot 2.2 開始,可以使用 @ConstructorBinding 註解來繫結配置屬性,而不是老式的 Setter 注入。

這基本上意味著 @ConfigurationProperties 註解的類現在可以是不可變的了。

在 Spring Boot 3 中,如果只有一個帶參建構函式,那麼建構函式繫結就是隱式的,不需要使用註解。但如果有多個建構函式,必須註解首選的那個:

@ConfigurationProperties(prefix = "mail.credentials")
public class ImmutableCredentials {

    private final String authMethod;
    private final String username;
    private final String password;

    @ConstructorBinding
    public ImmutableCredentials(String authMethod, String username, String password) {
        this.authMethod = authMethod;
        this.username = username;
        this.password = password;
    }

    public ImmutableCredentials(String username, String password) {
        this.username = username;
        this.password = password;
        this.authMethod = "Default";
    }
    public String getAuthMethod() {
        return authMethod;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}

如上,在使用 @ConstructorBinding 時,需要為建構函式提供想要繫結的所有引數。

注意,ImmutableCredentials 的所有欄位都是 final 欄位。此外,ImmutableCredentials 沒有 Setter 方法。

此外,需要強調的是,要使用建構函式繫結,需要透過 @EnableConfigurationProperties@ConfigurationPropertiesScan 明確啟用配置類。

9 Java 16 Record

Java 16 引入 Record 型別(JEP 395)。Record 類是不可變資料的透明載體。這使它們成為配置持有者和 DTO 的理想選擇。事實上,可以在 Spring Boot 中將 Java Record 定義為配置屬性。例如,前面的示例可重寫為:

@ConstructorBinding
@ConfigurationProperties(prefix = "mail.credentials")
public record ImmutableCredentials(String authMethod, String username, String password) {
}

顯然,它比那些模板式的 Getter 和 Setter 更簡潔。

Spring Boot 2.6 開始,對於單建構函式 Record,可以不用 @ConstructorBinding 註解。但是,如果 Record 有多個建構函式,則仍應使用 @ConstructorBinding 來標識用於屬性繫結的建構函式。

10 總結

本文介紹瞭如何在 Spring Boot 中使用 @ConfigurationProperties 來繫結配置屬性,以及如何進行屬性驗證和屬性轉換。

關注我,緊跟本系列專欄文章,咱們下篇再續!

作者簡介:魔都架構師,多家大廠後端一線研發經驗,在分散式系統設計、資料平臺架構和AI應用開發等領域都有豐富實踐經驗。

各大技術社群頭部專家博主。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。

負責:

  • 中央/分銷預訂系統效能最佳化
  • 活動&券等營銷中臺建設
  • 交易平臺及資料中臺等架構和開發設計
  • 車聯網核心平臺-物聯網連線平臺、大資料平臺架構設計及最佳化
  • LLM Agent應用開發
  • 區塊鏈應用開發
  • 大資料開發挖掘經驗
  • 推薦系統專案

目前主攻市級軟體專案設計、構建服務全社會的應用系統。

參考:

  • 程式設計嚴選網

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章