SpringBoot基礎系列之自定義配置源使用姿勢例項演示

一灰灰Blog發表於2021-06-12

【SpringBoot基礎系列】自定義配置源的使用姿勢介紹

前面一篇博文介紹了一個@Value的一些知識點,其中提了一個點,@Value對應的配置,除了是配置檔案中之外,可以從其他的資料來源中獲取麼,如從 redis,db,http 中獲取配置?

瞭解過 SpringCloud Config 的可以給出確切的答案,可以,而且用起來還老爽了,遠端配置,支援配置動態重新整理,接下來我們來看一下,在 SpringBoot 中,如何配置自定義的資料來源

I. 專案環境

1. 專案依賴

本專案藉助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA進行開發

開一個 web 服務用於測試

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

II. 自定義配置源

@Value修飾的成員,繫結配置時,是從Envrionment中讀取配置的,所以我們需要做的就是註冊一個自定義的配置源,藉助MapPropertySource可以來實現我們需求場景

1. 自定義資料來源

演示一個最簡單自定義的配置資料來源,重寫MapPropertySourcegetProperties方法

實現如下

public class SimplePropertiesSource extends MapPropertySource {
    public SimplePropertiesSource(String name, Map<String, Object> source) {
        super(name, source);
    }

    public SimplePropertiesSource() {
        this("filePropertiesSource", new HashMap<>());
    }

    /**
     * 覆蓋這個方法,適用於實時獲取配置
     *
     * @param name
     * @return
     */
    @Override
    public Object getProperty(String name) {
        // 注意,只針對自定義開頭的配置才執行這個邏輯
        if (name.startsWith("selfdefine.")) {
            return name + "_" + UUID.randomUUID();
        }
        return super.getProperty(name);
    }
}

2. 資料來源註冊

上面只是宣告瞭配置源,接下來把它註冊到 Environment 中,這樣就可以供應用使用了

@RestController
@SpringBootApplication
public class Application {
    private Environment environment;

    @Bean
    public SimplePropertiesSource simplePropertiesSource(ConfigurableEnvironment environment) {
        this.environment = environment;
        SimplePropertiesSource ropertiesSource = new SimplePropertiesSource();
        environment.getPropertySources().addLast(ropertiesSource);
        return ropertiesSource;
    }

    // 獲取配置
    @GetMapping(path = "get")
    public String getProperty(String key) {
        return environment.getProperty(key);
    }

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

從上面的輸出可以看出,自定義配置開頭的會獲取到隨機的配置值;非selfdefine開頭的,沒有相應的配置,返回空

3. 基於檔案的自定義配置源

上面這個可能有點過於兒戲了,接下來我們將配置源放在自定義的檔案中,並支援檔案配置修改

public class FilePropertiesSource extends MapPropertySource {
    public FilePropertiesSource(String name, Map<String, Object> source) {
        super(name, source);
    }

    public FilePropertiesSource() {
        this("filePropertiesSource", new HashMap<>());
    }

    // 這種方式,適用於一次撈取所有的配置,然後從記憶體中查詢對應的配置,提高服務效能
    // 10s 更新一次
    @PostConstruct
    @Scheduled(fixedRate = 10_000)
    public void refreshSource() throws IOException {
        String ans =
                FileCopyUtils.copyToString(new InputStreamReader(FilePropertiesSource.class.getClassLoader().getResourceAsStream("kv.properties")));
        Map<String, Object> map = new HashMap<>();
        for (String sub : ans.split("\n")) {
            if (sub.isEmpty()) {
                continue;
            }
            String[] kv = StringUtils.split(sub, "=");
            if (kv.length != 2) {
                continue;
            }

            map.put(kv[0].trim(), kv[1].trim());
        }

        source.clear();
        source.putAll(map);
    }
}

上面寫了一個定時器,每 10s 重新整理一下記憶體中的配置資訊,當然這裡也是可以配置一個檔案變動監聽器,相關有興趣的話,可以看下Java 實現檔案變動的監聽可以怎麼玩

對應的配置檔案

user=xhh
name=一灰灰
age=18

註冊的姿勢與上面一致,就不單獨說明了,接下來演示一下使用

從上可以看到檔案中的配置修改之後,過一段時間會重新整理

4. @Value繫結自定義配置

接下來我們看一下,將@Value繫結自定義的配置,是否可以成功

調整一下上面的 Application, 新增一個成員屬性

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

@GetMapping(path = "get")
public String getProperty(String key) {
    return name + "|" + environment.getProperty(key);
}

再次測試發現拋異常了,說是這個配置不存在!!!

(這就過分了啊,看了半天,結果告訴我不行,這還不得趕緊搞個差評麼 ???)

已經寫到這裡了,當然我也得繼續嘗試挽救一下,為啥前面直接通過Environment可以拿到配置,但是@Value註解繫結就不行呢?

”罪魁禍首“就在於初始化順序,我自定義的配置源,還沒有塞到Envrionment,你就開會著手繫結了,就像準備給”一灰灰 blog“一個差評,結果發現還沒關注...(好吧,我承認沒關注也可以評論 ?)

根據既往的知識點(至於是哪些知識點,那就長話短說不了了,看下面幾篇精選的博文吧)

要解決這個問題,一個最簡單的方式如下

建立一個獨立的配置類,實現自定義資料來源的註冊

@Configuration
public class AutoConfig {
    @Bean
    public FilePropertiesSource filePropertiesSource(ConfigurableEnvironment environment) {
        FilePropertiesSource filePropertiesSource = new FilePropertiesSource();
        environment.getPropertySources().addLast(filePropertiesSource);
        return filePropertiesSource;
    }
}

測試類上指定 bean 依賴

@DependsOn("filePropertiesSource")
@EnableScheduling
@RestController
@SpringBootApplication
public class Application {
    @Autowired
    private Environment environment;

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

    @GetMapping(path = "get")
    public String getProperty(String key) {
        return name + "|" + environment.getProperty(key);
    }

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

再次測試,結果如下

從上面的演示動圖可以看到,繫結自定義的資料來源配置,沒有問題,但是,當配置變更時,繫結的 name 欄位,沒有隨之更新

簡單來講就是不支援動態重新整理,這就難受了啊,我就想要動態重新整理,那該怎麼搞?

  • 不要急,新的博文已經安排上了,下篇奉上(怕迷路的小夥伴,不妨關注一下”一灰灰 blog“?)

5. 小結

最後按照慣例小結一下,本文篇幅雖長,但知識點比較集中,總結下來,兩句話搞定

  • 通過繼承MapPropertySource來實現自定義配置源,註冊到Envrionment可供@Value使用
  • 使用@Value繫結自定義配置源時,注意註冊的順序要早於 bean 的初始化

好的,到這裡正文結束, 我是一灰灰,歡迎各位大佬來踩一踩長草的公眾號"一灰灰 blog"

III. 不能錯過的原始碼和相關知識點

0. 專案

配置系列博文

1. 一灰灰 Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

一灰灰blog

相關文章