巧用SpringBoot擴展點EnvironmentPostProcessor

我們的項目是單體項目,使用的是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包依賴,理想情況下什麼都不需要配置就可以直接使用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章