巧用SpringBoot擴充套件點EnvironmentPostProcessor

狂盜一枝梅發表於2021-07-12

我們的專案是單體專案,使用的是springboot的框架,隨著對接的外部服務越來越多,配置檔案越來越臃腫。。我們將對接的外部服務的程式碼單獨抽離出來形成service依賴,之後以jar包的形式引入,這時候外部服務配置放到哪裡算是個難題了,我主張將配置檔案附著在service依賴中,這樣主專案的配置檔案將會非常整潔。這裡舉個例子,A專案是主專案,B、C兩個專案分別是對接外部服務B、C的Service專案,我將對接B的配置檔案放到B專案,將對接C專案的配置檔案放到C專案,A直接引入B、C的依賴即可直接使用,不用在A專案中再單獨配置對接B、C專案的配置了。

要想實現上面的功能,需要使用到SpringBoot的擴充套件點功能EnvironmentPostProcessor

一、EnvironmentPostProcessor的使用

官方文件:https://docs.spring.io/spring-boot/docs/2.5.2/reference/htmlsingle/#howto.application.customize-the-environment-or-application-context

該類的作用是在SpringBoot專案啟動之前自定義環境變數,可以在專案啟動之前從非標準springboot配置檔案中讀取相關的配置並填充到springboot上下文中。

1.實現EnvironmentPostProcessor 介面

對於properties檔案

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Resource path = new ClassPathResource("com/example/myapp/config.yml");
        PropertySource<?> propertySource = loadYaml(path);
        environment.getPropertySources().addLast(propertySource);
    }

    private PropertySource<?> loadYaml(Resource path) {
        Assert.isTrue(path.exists(), () -> "Resource " + path + " does not exist");
        try {
            return this.loader.load("custom-resource", path).get(0);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
        }
    }

}

對於yaml檔案

@Slf4j
@Order
public class YamlExtPluginProcessor implements EnvironmentPostProcessor {

    private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Resource path = new ClassPathResource("application-AAA.yaml");
        if (!path.exists()) {
            throw new IllegalArgumentException("Resource " + path + " does not exists");
        }
        try {
            List<PropertySource<?>> load = loader.load("application-AAA", path);
            log.info("發現了{}個配置檔案", load.size());
            for (PropertySource<?> propertySource : load) {
                environment.getPropertySources().addLast(propertySource);
            }
            log.info("已載入 {} 配置檔案", "application-AAA.yaml");
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to load yaml configuration from " + path, e);
        }
    }
}

2.在resources資原始檔夾中新建META-INF/spring.factories檔案

填充內容

org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor

如果只是主專案中需要配置額外的配置檔案,只需要做到這裡就能滿足需求了,但是在我的使用場景中,並不能滿足需求,我的需求是A外部依賴B、C,而這些配置要放到B、C,B和C不可執行,只是Service依賴,儘管大多數的使用都一樣,但是還是有所不同。

二、外部依賴式配置

A專案resources目錄

│  application-A.yaml
│
└─META-INF
        spring.factories

B專案resources目錄

│  application-B.yaml
│
└─META-INF
        spring.factories

然後分別在A、B專案中實現EnvironmentPostProcessor介面讀取相關的配置檔案,並註冊到spring.factories檔案即可。

1.配置檔名字問題

配置檔名一定要保持唯一,這裡在resources目錄下新建application-xxx.properties配置檔案,xxx對應著專案名,這樣好記還能保持唯一性。如果配置檔名不唯一又會如何呢?如果配置檔名字都寫作application-plugin.yaml,A專案有一個,B專案也有一個,則如果A專案中的先生效了,B專案中的配置檔案將會被直接忽略。所以配置檔名字不能有重複的。

關於配置檔案的載入先後順序和位置問題,可以參考文件:https://blog.csdn.net/J080624/article/details/80508606

官方文件:https://docs.spring.io/spring-boot/docs/2.5.2/reference/htmlsingle/#features.external-config

2.EnvironmentPostProcessor優先順序問題

image-20210712143141838

官方文件中對於優先順序問題有這麼個提示,大意是我們讀取了配置並將其放到了配置的最後,或許應當定義一個優先順序以讓配置在合適的情況下生效。

我的需求裡,B專案和C專案的依賴中並不是放了所有的對接B、C服務的配置,而是大部分不可變的配置放到B、C,比如請求B/C服務的url;少部分不同環境不同配置的配置項放到可變的主專案的配置中,比如請求的認證資訊,測試環境和生產環境不一樣,那就要分別放到A專案的測試環境配置、生產環境配置檔案中。

我需要B、C專案中沒有但是A專案中有的配置,要全部配置一起生效;B、C專案中有的配置,A專案中也有的配置,要A專案中的生效。

B、C作為一個配角,可不能搶了主角A的戲。

解決方法就是什麼都不做,或者只是加一個@Order註解到EnvironmentPostProcessor實現類上,使用預設最低的優先順序;如果使用了最高的優先順序,則會“喧賓奪主”,B和C專案會覆蓋主專案A中的同名配置。

三、其它引入外部配置的方法

其實說起來很簡單,只需要使用

spring:
  profiles:
    include: B,C

該配置將需要的外部配置檔案引入進來即可,但是有侷限性

  1. 需要外部配置檔案的位置放到resources目錄下並且配置檔名一定得是application-xxx.properties,符合springboot的命名規範才行,當然配置檔名字也不能一樣
  2. 需要手動修改主專案A的配置,這個需要使用者反編譯引入的jar包才能知道該如何做,增加了使用的複雜度

所以還是使用EnvironmentPostProcessor擴充套件點最好,使用者只需要引入jar包依賴,理想情況下什麼都不需要配置就可以直接使用了。

相關文章