1. 程式人生 > >SpringBoot(31) - Properties(3) - 型別安全的配置屬性

SpringBoot(31) - Properties(3) - 型別安全的配置屬性

參考:https://docs.spring.io/spring-boot/docs/1.5.17.RELEASE/reference/htmlsingle/#boot-features-external-config-typesafe-configuration-properties

 

使用@Value("$ {property}")註解來注入配置屬性有時會很麻煩,特別是如果正在使用多個屬性或者資料本質上是分層的。 Spring Boot提供了一種使用屬性的替代方法,允許強型別bean管理和驗證應用程式的配置。

package com.example;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("foo")
public class FooProperties {
    private boolean enabled;
    private InetAddress remoteAddress;
    private final Security security = new Security();

    public boolean isEnabled() { ... }
    public void setEnabled(boolean enabled) { ... }
    public InetAddress getRemoteAddress() { ... }
    public void setRemoteAddress(InetAddress remoteAddress) { ... }

    public Security getSecurity() { ... }

    public static class Security {
        private String username;
        private String password;
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        public String getUsername() { ... }
        public void setUsername(String username) { ... }
        public String getPassword() { ... }
        public void setPassword(String password) { ... }
        public List<String> getRoles() { ... }
        public void setRoles(List<String> roles) { ... }

    }
}

上面的POJO定義了以下屬性:

  • foo.enabled:預設值false
  • foo.remote-address:從String強制轉換
  • foo.security.username:使用巢狀的“security”,其名稱由屬性的名稱決定。 特別是返回型別根本沒有使用,可能是SecurityProperties
  • foo.security.password:
  • foo.security.roles:String集合

注:getter和setter通常是必需的,因為繫結是通過標準的Java Beans屬性描述符,就像在Spring MVC中一樣。 有些情況下可能會省略setter:

  • Map,只要它們被初始化,就需要一個getter,但setter不是必需的,因為它們可以被繫結器轉換。
  • 可以通過索引(通常使用YAML)或使用單個逗號分隔值(屬性)訪問集合和陣列。 在後一種情況下,必須使用setter。 建議始終為此型別新增setter。 如果初始化集合,請確保它是可變的(如上例所示)
  • 如果初始化巢狀的POJO屬性(如上例中的“security”欄位),則不需要setter。 如果希望繫結器使用其預設建構函式即時建立例項,則需要一個setter。

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

 

還需要列出要在@EnableConfigurationProperties註解中註冊的屬性類:

@Configuration
@EnableConfigurationProperties(FooProperties.class)
public class MyConfiguration {
}

注:

  • 當@ConfigurationProperties bean以這種方式註冊時,bean將具有常規名稱:<prefix>-<fqn>,其中<prefix>是@ConfigurationProperties註解中指定的環境鍵字首,<fqn>是bean的完全限定名稱。如果註解未提供任何字首,則僅使用bean的完全限定名稱。
  • 上例中的bean名稱為foo-com.example.FooProperties。

即使上面的配置會為FooProperties建立一個常規bean,建議@ConfigurationProperties只處理環境,特別是不從上下文中注入其他bean。 話雖如此,@EnableConfigurationProperties註解也會自動應用於專案,以便從Environment配置任何使用@ConfigurationProperties註解的現有bean。 可以通過確保FooProperties已經是一個bean來連結上面的MyConfiguration:

@Component
@ConfigurationProperties(prefix="foo")
public class FooProperties {
    // ... see above
}

這種配置風格特別適用於SpringApplication外部YAML配置:

# application.yml
foo:
    remote-address: 192.168.1.1
    security:
        username: foo
        roles:
          - USER
          - ADMIN
# additional configuration as required

要使用@ConfigurationProperties bean,可以像使用任何其他bean一樣注入它們。

@Service
public class MyService {
    private final FooProperties properties;

    @Autowired
    public MyService(FooProperties properties) {
        this.properties = properties;
    }
     //...
    @PostConstruct
    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }
}

注:使用@ConfigurationProperties還可以生成可供IDE使用的元資料檔案,以便為自己的金鑰提供自動完成功能,有關詳細資訊,請參閱附錄B,配置元資料附錄。

 

1. 第三方配置

除了使用@ConfigurationProperties來註釋類之外,還可以在公共@Bean方法上使用它。 當想要將屬性繫結到控制元件之外的第三方元件時,這可能特別有用。
要從Environment屬性配置bean,請將@ConfigurationProperties新增到其bean註冊:

@ConfigurationProperties(prefix = "bar")
@Bean
public BarComponent barComponent() {
    ...
}

使用bar字首定義的任何屬性都將以與上面的FooProperties示例類似的方式對映到該BarComponent bean。

 

2. 鬆綁定

Spring Boot使用一些寬鬆的規則將Environment屬性繫結到@ConfigurationProperties bean,因此不需要在Environment屬性名和bean屬性名之間進行精確匹配。 這有用的常見示例包括虛線分隔(例如,context-path繫結到contextPath)和大寫(例如PORT繫結到埠)環境屬性。
例如,給定以下@ConfigurationProperties類:

@ConfigurationProperties(prefix="person")
public class OwnerProperties {
    private String firstName;

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

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

可以使用以下屬性名稱:

  • user.headPic:標準駝峰語法
  • user.head-pic:虛線表示法,推薦在.properties和.yml檔案中使用
  • user.head_pic:下劃線表示法,在.properties和.yml檔案中使用的可替代格式
  • USER_HEAD_PIC:大寫格式,推薦系統變數使用

 

3. 屬性轉換

當Spring繫結到@ConfigurationProperties bean時,它將嘗試將外部應用程式屬性強制轉換為正確的型別。 如果需要自定義型別轉換,可以提供ConversionService bean(帶有bean id conversionService)或自定義屬性編輯器(通過CustomEditorConfigurer bean)或自定義轉換器(帶有註釋為@ConfigurationPropertiesBinding的bean定義)。

注:由於在應用程式生命週期中很早就請求了此bean,因此請確保限制ConversionService正在使用的依賴項。 通常,在建立時可能無法完全初始化所需的任何依賴項。 如果配置金鑰強制不需要,可能希望重新命名自定義ConversionService,並且只依賴於使用@ConfigurationPropertiesBinding限定的自定義轉換器。

 

4. @ConfigurationProperties校驗

只要用Spring的@Validated註解註釋,Spring Boot就會嘗試驗證@ConfigurationProperties類。 可以直接在配置類上使用JSR-303 javax.validation約束註解。 只需確保符合條件的JSR-303實現在類路徑中,然後向欄位新增約束註解:

@ConfigurationProperties(prefix="foo")
@Validated
public class FooProperties {
    @NotNull
    private InetAddress remoteAddress;
    // ... getters and setters
}

為了驗證巢狀屬性的值,必須將關聯欄位註釋為@Valid以觸發其驗證。 例如,在上面的FooProperties示例的基礎上:

@ConfigurationProperties(prefix="connection")
@Validated
public class FooProperties {
    @NotNull
    private InetAddress remoteAddress;
    @Valid
    private final Security security = new Security();

    // ... getters and setters

    public static class Security {
        @NotEmpty
        public String username;
        // ... getters and setters
    }
}

還可以通過建立名為configurationPropertiesValidator的bean定義來新增自定義Spring Validator。 應該將@Bean方法宣告為static。 配置屬性驗證器是在應用程式生命週期的早期建立的,並將@Bean方法宣告為static,允許建立bean而無需例項化@Configuration類。 這避免了早期例項化可能導致的任何問題。 有一個屬性驗證示例,可以看到如何設定。

注:spring-boot-actuator模組包括一個暴露所有@ConfigurationProperties bean的端點。 只需將Web瀏覽器指向/configprops或使用等效的JMX端點即可。 

 

5. @ConfigurationProperties對比@Value

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

特性 @ConfigurationProperties @Value

鬆綁定

Yes

No

元資料的支援

Yes

No

SpEL表示式

No

Yes

如果為自己的元件定義一組配置鍵,建議將它們分組到使用@ConfigurationProperties註釋的POJO中。 另請注意,由於@Value不支援寬鬆繫結,因此如果需要使用環境變數提供值,則它不是一個很好的選擇。
最後,雖然可以在@Value中編寫SpEL表示式,但不會從Application屬性檔案處理此類表示式。