Spring Boot 基礎: 使用 `@ConfigurationProperties` 實現自定義屬性的自動裝配

超悠閒發表於2020-12-18

Spring Boot 基礎: 使用 @ConfigurationProperties 實現自定義配置屬性的自動裝配

簡介

在 Spring Boot 的實踐中我們有時會需要針對整個專案進行全域性配置,可能是應用的部署資訊、依賴配置資訊,甚至是服務需要的全域性引數等。通常我們都會將配置資訊寫在 resources/application.yml(或是 .properties) 內,接下來我們可以透過幾種手段來提取配置資訊,接下來讓我娓娓道來。

參考

SPRINGBOOT用@CONFIGURATIONPROPERTIES獲取配置檔案值https://www.cnblogs.com/lihaoyang/p/10223339.html
Spring Boot 配置檔案注入,在yml中可以自動提示https://www.cnblogs.com/vianzhang/p/13615123.html
SPRINGBOOT專案啟動成功後執行一段程式碼的兩種方式https://www.cnblogs.com/zuidongfeng/p/9926471.html
Spring Boot中自定義屬性的程式碼完成https://www.mdoninger.de/2015/05/16/completion-for-custom-properties-in-spring-boot.html

正文

提前預覽

首先我們先預覽下面提到的各個註解或依賴的作用:

  1. @Value 引入單個配置屬性值
  2. @ConfigurationProperties 宣告配置屬性的對映型別
  3. @EnableConfigurationProperties 允許掛載配置物件,同時也是配置物件註冊 Bean 的入口
  4. spring-boot-configuration-processor 自動生成配置屬性說明檔案,提供程式設計時的程式碼提示

@Value

首先我們第一個想到的方法是使用 @Value("${xxx}") 註解來獲取配置檔案的配置項,使用示例如下:

application.yml

server:
  port: 8800

DemoController.java

@RestController
class DemoController {
    
    @Value("${server.port}")
    private Integer port;

    @GetMapping("/")
    public String info() {
        return "Default route from localhost:" + port;
    }
}

這樣一來我們就完成配置項的引入,但是如果需要配置的專案越來越多結構越來越複雜呢?還要像這樣一個個引入嗎?

application.yml

my:
  prop:
    name: superfree
    port: 8800
    exclude-users:
      - user1
      - user2
      - user3
    server:
      name: my-server
      url: http://localhost:8801
    client:
      name: my-client
      url: http://localhost:8802

DemoController.java

@RestController
class DemoController {
    
    @Value("${my.prop.name}")
    private String name;
    @Value("${my.prop.port}")
    private Integer port;
    @Value("${my.prop.exclude-users}")
    private List<String> excludeUsers;
    @Value("${my.prop.server.name}")
    private String serverName;
    @Value("${my.prop.server.url}")
    private String serverUrl;
    @Value("${my.prop.client.name}")
    private String clientName;
    @Value("${my.prop.client.url}")
    private String clientUrl;
    // ...
}

難不成要像這樣一個個引入嗎?這樣也太醜而且有夠麻煩hhh,所以通常我們選擇採取另一個做法

@ConfigurationProperties

@ConfigurationProperties 這個註解可以宣告一個配置屬性對應的型別,然後我們就可以使用自動裝配的功能,宣告方式如下:

application.yml:配置項結構

my:
  prop:
    name: superfree
    port: 8800

MyProperties.java


// 透過 prefix 引數宣告字首
@ConfigurationProperties(prefix = "my.prop")
public class MyProperties {

    // 與配置檔案屬性一一對應
    private String name;
    private Integer port;

    // getter/setter for name & port
}

@EnableConfigurationProperties

配置檔案和對映型別都寫好了,接下來我們必須找到一個裝配該型別的地方。

一種方法是將該配置類加上 @Component,作為一個"元件"注入

@Component // 宣告為獨立的元件
@ConfigurationProperties(prefix = "my.prop")
public class MyProperties {
    // ...

第二種做法是使用 @EnableConfigurationProperties 將配置類掛載到我們要使用的類上,而這個類通常可以是某個配置類或是其他元件(Component)

@Configuration // or @Component
@EnableConfigurationProperties(MyProperties.class)
public class MyConfig {

    // 使用 EnableConfigurationProperties 之後就可以透過 @Autowired 實現自動裝配
    @Autowired
    private MyProperties myProperties;
    // ...

or

@RestController
@EnableConfigurationProperties(MyProperties.class)
public class DemoController {

    @Autowired
    private MyProperties myProperties;
    // ...

透過這種方式我們就可以藉由宣告配置項的對映型別即可實現自動裝配,非常便利。

spring-boot-configuration-processor

最後一件事情是,如果我們的服務被打包成第三方依賴,或是本身開發過程中,我們可能希望 IDE 能更智慧的根據對映物件來提示我們到底該配置哪些屬性,我們就可以用上 spring-boot-configuration-processor 依賴,在 maven 新增下方依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

這時候我們再手動的呼叫 mvn clean package(IDEA 有提供視覺化按鈕直接使用),我們就會在打包後的 target 專案中看到關於配置項的結構說明檔案 spring-configuration-metadata.json

這樣一來使用我們的包作為依賴的開發者,他的 IDE 就能夠根據這個檔案在 yml 中給出相應的程式碼提示。

若我們想要在自己開發的過程中也享受到程式碼提示我們可以選擇將 META-INF 目錄整個複製到 resoureces 目錄之下(如下圖),即可實現相同效果

完整程式碼示例

最後我們給出完整示例的關鍵程式碼,詳細完整專案在這裡

application.yml:配置專案(實現巢狀物件配置)

my:
  prop:
    name: superfree
    port: 8800
    exclude-users:
      - user1
      - user2
      - user3
    server:
      name: my-server
      url: http://localhost:8801
    client:
      name: my-client
      url: http://localhost:8802

MyProperties.java:配置項型別


@ConfigurationProperties(prefix = "my.prop")
public class MyProperties {

    private String name;
    private Integer port;
    private List<String> excludeUsers;
    private ServerProperties server;
    private ClientProperties client;
    // toString, getter/setter
}

ServerProperties.java:巢狀型別1

// 注意,這邊就不需要 ConfigurationProperties 了
public class ServerProperties {

    private String name;
    private String url;
    // toString, getter/setter
}

ClientProperties.java:巢狀型別2

public class ClientProperties {

    private String name;
    private String url;
    // toString, getter/setter
}

Runner.java:測試類

@Component
@EnableConfigurationProperties(MyProperties.class)
public class Runner implements ApplicationRunner {

    @Autowired
    private MyProperties myProperties; // 自動裝配配置項

    @Override
    public void run(ApplicationArguments args) throws Exception {
        show();
    }

    public void show() {
        System.out.println("----- show my.prop -----");
        System.out.println(myProperties);
    }
}

輸出

----- show my.prop -----
MyProperties{name='superfree', port=8800, excludeUsers=[user1, user2, user3], server=ServerProperties{name='my-server', url='http://localhost:8801'}, client=ClientProperties{name='my-client', url='http://localhost:8802'}}

結語

好啦到此結束啦,大家都學會如何優雅的引入配置檔案的內容了嗎,再也不用看到一大票醜醜的 @Value 了。

相關文章