• 巧用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包依赖,理想情况下什么都不需要配置就可以直接使用了。

  • 相关阅读:
    从迷宫终点出发——Leo鉴书36
    OCP-1Z0-053-V13.02-238题
    OCP-1Z0-053-V13.02-233题
    OCP-1Z0-053-V13.02-232题
    OCP-1Z0-053-V13.02-228题
    OCP-1Z0-053-V13.02-226题
    OCP-1Z0-053-V13.02-225题
    OCP-1Z0-053-V13.02-221题
    OCP-1Z0-053-V13.02-219题
    OCP-1Z0-053-V13.02-216题
  • 原文地址:https://www.cnblogs.com/kuangdaoyizhimei/p/15002004.html
Copyright © 2020-2023  润新知