.yml文件_Spring Boot实现加载自定义配置文件

20e7caaf0f80c0990aa878bfbecc8ae2.png关注 “Java艺术” 我们一起成长! 你好,我是吴就业, 这是我的第170篇原创文章,欢迎阅读!

64530571673ab462030592a0eda653f4.png

或许你也发现了,在配置项多的情况下, application-xx.yml 配置文件显得过于臃肿,并且在一个分布式项目中,数据库、 redis 等配置通常是每个微服务都会用到的配置,也都是相同的配置。 为了解决单一配置文件过于臃肿的问题,并且实现让多个微服务共用一些配置文件,我们在新项目中将以往的单配置文件拆分成了多个配置文件。 另外,我们使用 kubernetesConfigMap 资源作为“配置中心”,可以为每个配置文件创建一个 ConfigMap 资源,每个微服务项目需要哪些配置文件就可以只引用哪些 ConfigMap 资源。 spring-cloud-kubernete-config 会自动读取引用的 ConfigMap 资源中的配置信息,并写入到 Environment 中。 虽然通过配置中心加载配置可以去掉配置文件,但本地测试我们通常不会通过配置中心去读取,因此,将单一配置文件拆分为多个配置文件之后,本地测试如何让 SpringBoot 加载这些配置文件就是我们要解决的问题。

b06cd8318463b1e472926f3066e503b7.png

本篇将介绍两种加载自定义配置文件的实现方式,并通过分析源码了解 SpringBoot 加载配置文件的流程,从而加深理解。 SpringBoot加载配置文件的原理 要实现加载自定义 yml 文件,我们先要了解 SpringBoot 是在何时,以及如何加载 application-xx.yml 配置文件的,为什么配置 spring.profiles.active 就能导入相应的配置文件。 通过猜测,配置文件的加载应该在容器初始化之前,因为我们经常会在 Configuration 中就要使用到一些配置,如果在 Configuration 开始工作之前,配置还没有加载,必然会抛出异常。 根据猜测,我们找到 SpringApplication#run 方法,如下图所示。 1d39be9057eec63cbd4b53a1ed5ee767.png SpringBoot 在创建 ApplicationContext 之前,会先调用 prepareEnvironment 方法准备创建容器所需要的环境,即创建 Environment ,并加载配置到 Environment 。这个过程中还会调用 SpringApplicationRunListeners#environmentPrepared 方法发布 Environment 准备事件。 1d5b345cbd7ca29f49dba886cdc007eb.png 执行上图中画线的代码最终会调用 EventPublishingRunListener#environmentPrepared 方法,该方法广播一个 ApplicationEnvironmentPreparedEvent 事件(事件同步广播同步消费),只要实现 ApplicationListener 接口并且订阅 ApplicationEnvironmentPreparedEvent 事件的订阅者都会接收到该事件, onApplicationEvent 方法被调用。 由于 Spring 实现事件的发布订阅是同步的,在不清楚到底有多少个 ApplicationEnvironmentPreparedEvent 事件订阅者、不知道哪个订阅者才是负责加载 spring.profiles.active 配置项指定环境的配置文件时,我们可通过下断点调试方式一步步查找。我们也可以通过 IDEA 快速查找都有哪些类引用了 ApplicationEnvironmentPreparedEvent ,如下图所示。 355ba76a55012285c7c2582137e85462.png 最终找到 ConfigFileApplicationListener 这个订阅者,该订单者实现 ApplicationListener 接口,但只订阅两种类型的事件,如下图所示。 b6ff58897239ce64817ffa79a61cb9db.png 现在我们只关心 ConfigFileApplicationListener 是如何消费 ApplicationEnvironmentPreparedEvent 事件的,所以我们接着看 onApplicationEnvironmentPreparedEvent 方法,如下图所示。 ed1f6c8cfea70d87dc6fe437cb3064f9.png Spring 框架提供很多的前置处理器,我们所了解的 Bean 前置处理器可在 Bean 实例化后创建 Bean 的代理对象,将代理对象注入 Bean 工厂,而不是原对象。同样的, Spring 也提供 Environment 的前置处理器,用于往 Environment 中添加新的环境变量或者修改环境变量的值、移除环境变量。 从 ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent 方法可以看出,该方法首先调用 loadPostProcessors 方法获取所有的 EnvironmentPostProcessor ,通过 @Order 排序之后依次遍历调用 EnvironmentPostProcessor 对象的 postProcessEnvironment 方法。 由于环境准备阶段容器并未创建,更没有初始化,所以 EnvironmentPostProcessor 是无法通过 @Bean@Component 方式注册的。那 Spring 是怎么获取 EnvironmentPostProcessor 的呢,看下图。 7e8e26a8d1aa3eb583f1b59d4103f1a1.png loadPostProcessors 方法通过 SpringFactoriesLoaderspring.factories 文件中加载 EnvironmentPostProcessor 。所以,如果我们想自定义 EnvironmentPostProcessor 来添加环境变量,首先我们需要实现 EnvironmentPostProcessor 接口,然后将自定义的 EnvironmentPostProcessor 添加到 spring.factories 文件。 SpringBoot 实现的这种 factories 机制类似于 JavaSPI ,但 JavaSPI 只能配置接口的实现类,每个接口都需要一个配置文件, springfactories 机制则没有这种限制。 SpringBoot 默认配置的 EnvironmentPostProcessor 如下图所示。 9021b3e553bf1523a5e3968892723280.png 从名字来看,这些 EnvironmentPostProcessor 都与加载 application 配置文件无关。可我们疏忽了一点, ConfigFileApplicationListener 也实现了 EnvironmentPostProcessor 接口,并且在 onApplicationEnvironmentPreparedEvent 方法中也调用了自身的 postProcessEnvironment 方法,如下图所示。 db46f89dfc02434ca5807905995954dd.png 如果你看 ConfigFileApplicationListener 的源码,也能从它的一些静态变量看出它就是负责加载 spring.profiles.activespring.profiles.include 配置项指定配置文件的 EnvironmentPostProcessor ,如下图所示。 1647d1f25fb66a9b3ed143cab531bbfa.png 具体的实现就不往下分析了。 通过spring.profiles.include导入 实现加载自定义配置文件最简单的方式,我们可以通过配置 spring.profiles.include 导入指定的自定义配置文件,这是 springboot 为我们提供的拆分配置文件的功能,但配置文件的命令必须以 application- 开头。 如本地测试将 spring.profiles.active 配置为 dev ,则会导入 application-dev.yml 配置文件,我们只需要在 application-dev.yml 中配置 spring.profiles.include 导入用于测试环境的自定义配置文件即可。 例如导入 application-rds-dev.yml ,则配置如下。
spring:  profiles:    include: rds-dev
除此之外,我们还可以直接在 application.yml 配置文件中配置 spring.profiles.include ,例如:
spring:  profiles:    active: ${SPRING_PROFILES_ACTIVE:dev}    include: rds-${SPRING_PROFILES_ACTIVE:dev}
在本例中,使用 ${SPRING_PROFILES_ACTIVE:dev} 根据环境(测试环境、预发布环境、生产环境)选择不同的 rds 配置文件。 当 SPRING_PROFILES_ACTIVE 变量不存在时,则默认为 dev 环境, include 导入 application-rds-dev.yml 配置文件;如果是生产环境,则 SPRING_PROFILES_ACTIVEprd (在我们项目中 prd 为什么环境), include 将导入 application-rds-prd.yml 配置文件。 通过 java 命令启动 springboot 应用,可以在启动时再通过 -Dspring.profiles.active 参数切换配置,而本例使用环境变量主要是解决将应用构建为 Docker 镜像时,无法在启动时再通过 -Dspring.profiles.active 参数切换配置的问题。 通过自定义EnvironmentPostProcessor导入 通过配置 spring.profiles.include 导入自定义文件有一个强制约定,文件名必须以 application- 开头。 在不想使用 application- 作为文件名前缀的情况下,并且想让 SpringBoot 能够根据环境选择是否加载 resources 目录下的自定义配置文件时,就无法使用 spring.profiles.include 。 那有没有一种方式能够实现更灵活的加载自定义配置文件?通过前面对 SpringBoot 加载配置文件的了解,相信你已经有了答案。没错,可是通过自定义 EnvironmentPostProcessor 实现。 将配置文件拆分后,我们将文件改为以 common- 开头,例如: common-rdscommon-redis 。如果是线上环境直接从配置中心读取,只在本地测试不想从配置中心读取的情况下,自定义的 EnvironmentPostProcessor 才会加载自定义配置文件。 通过自定义 EnvironmentPostProcessor 加载自定义配置文件,导入配置信息,整体上只需要两步: 第一步:自定义 ProfileEnvironmentPostProcessor 实现 EnvironmentPostProcessor 接口,代码如下。
public class ProfileEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {// .......// 加载配置
PropertySource> source = loadProfiles(resource);// 添加到Environment
environment.getPropertySources().addFirst(source);
}
}
loadProfiles 方法实现如下,通过 YamlPropertySourceLoader 解析 yml 配置文件。
private PropertySource> loadProfiles(Resource resource) {
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
List> propertySources = sourceLoader.load(resource.getFilename(), resource);return propertySources.get(0);}
第二步:将 ProfileEnvironmentPostProcessor 配置到 spring.factories ,配置如下。
org.springframework.boot.env.EnvironmentPostProcessor=\
com.xxx.spring.profile.ProfileEnvironmentPostProcessor
最后,我们也可以将 ProfileEnvironmentPostProcessor 封装成一个 starter 包,以便服务于每个微服务项目。 到现在,我们也只是实现了如何读取自定义配置文件,将配置写入 Environment 中。实际还有很多细节需要我们考虑,例如,如何判断只在 spring.profiles.active 配置为 dev 时才加载自定义文件、如何区分当前是准备启动 Spring Cloud 容器的环境还是准备启动 Spring Boot 容器的环境(前者最终变为后者的父容器),下面是笔者的实现,仅供参考。 fde64afc931a39b517d94c778dba5891.png
  • 通过在bootstrap.yaml配置文件中配置spring.cloud.config.choose指定当前应用需要导入哪些配置文件。当spring.profiles.active配置为dev时才去加载spring.cloud.config.choose指定的配置文件。

  • 由于Spring Cloud启动的容器与Spring Boot启动的容器使用的不是同一个ProfileEnvironmentPostProcessor对象,但使用的是同一个类加载器加载的类,因此可以通过静态变量共享spring.cloud.config.choose配置。

6961945d040bde07e110ed47ff5a8af4.png [Java艺术] 微信号:javaskill一个只推送原创文章的技术公众号, 分享Java后端相关技术。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值