

application-xx.yml配置文件显得过于臃肿,并且在一个分布式项目中,数据库、
redis等配置通常是每个微服务都会用到的配置,也都是相同的配置。
kubernetes的
ConfigMap资源作为“配置中心”,可以为每个配置文件创建一个
ConfigMap资源,每个微服务项目需要哪些配置文件就可以只引用哪些
ConfigMap资源。
spring-cloud-kubernete-config会自动读取引用的
ConfigMap资源中的配置信息,并写入到
Environment中。
SpringBoot加载这些配置文件就是我们要解决的问题。

SpringBoot加载配置文件的流程,从而加深理解。
yml文件,我们先要了解
SpringBoot是在何时,以及如何加载
application-xx.yml配置文件的,为什么配置
spring.profiles.active就能导入相应的配置文件。
Configuration中就要使用到一些配置,如果在
Configuration开始工作之前,配置还没有加载,必然会抛出异常。
SpringApplication#run方法,如下图所示。

SpringBoot在创建
ApplicationContext之前,会先调用
prepareEnvironment方法准备创建容器所需要的环境,即创建
Environment,并加载配置到
Environment。这个过程中还会调用
SpringApplicationRunListeners#environmentPrepared方法发布
Environment准备事件。

EventPublishingRunListener#environmentPrepared方法,该方法广播一个
ApplicationEnvironmentPreparedEvent事件(事件同步广播同步消费),只要实现
ApplicationListener接口并且订阅
ApplicationEnvironmentPreparedEvent事件的订阅者都会接收到该事件,
onApplicationEvent方法被调用。
Spring实现事件的发布订阅是同步的,在不清楚到底有多少个
ApplicationEnvironmentPreparedEvent事件订阅者、不知道哪个订阅者才是负责加载
spring.profiles.active配置项指定环境的配置文件时,我们可通过下断点调试方式一步步查找。我们也可以通过
IDEA快速查找都有哪些类引用了
ApplicationEnvironmentPreparedEvent,如下图所示。

ConfigFileApplicationListener这个订阅者,该订单者实现
ApplicationListener<ApplicationEvent>接口,但只订阅两种类型的事件,如下图所示。

ConfigFileApplicationListener是如何消费
ApplicationEnvironmentPreparedEvent事件的,所以我们接着看
onApplicationEnvironmentPreparedEvent方法,如下图所示。

Spring框架提供很多的前置处理器,我们所了解的
Bean前置处理器可在
Bean实例化后创建
Bean的代理对象,将代理对象注入
Bean工厂,而不是原对象。同样的,
Spring也提供
Environment的前置处理器,用于往
Environment中添加新的环境变量或者修改环境变量的值、移除环境变量。
ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent方法可以看出,该方法首先调用
loadPostProcessors方法获取所有的
EnvironmentPostProcessor,通过
@Order排序之后依次遍历调用
EnvironmentPostProcessor对象的
postProcessEnvironment方法。
EnvironmentPostProcessor是无法通过
@Bean、
@Component方式注册的。那
Spring是怎么获取
EnvironmentPostProcessor的呢,看下图。

loadPostProcessors方法通过
SpringFactoriesLoader从
spring.factories文件中加载
EnvironmentPostProcessor。所以,如果我们想自定义
EnvironmentPostProcessor来添加环境变量,首先我们需要实现
EnvironmentPostProcessor接口,然后将自定义的
EnvironmentPostProcessor添加到
spring.factories文件。
SpringBoot实现的这种
factories机制类似于
Java的
SPI,但
Java的
SPI只能配置接口的实现类,每个接口都需要一个配置文件,
spring的
factories机制则没有这种限制。
SpringBoot默认配置的
EnvironmentPostProcessor如下图所示。

EnvironmentPostProcessor都与加载
application配置文件无关。可我们疏忽了一点,
ConfigFileApplicationListener也实现了
EnvironmentPostProcessor接口,并且在
onApplicationEnvironmentPreparedEvent方法中也调用了自身的
postProcessEnvironment方法,如下图所示。

ConfigFileApplicationListener的源码,也能从它的一些静态变量看出它就是负责加载
spring.profiles.active、
spring.profiles.include配置项指定配置文件的
EnvironmentPostProcessor,如下图所示。

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_ACTIVE为
prd(在我们项目中
prd为什么环境),
include将导入
application-rds-prd.yml配置文件。
java命令启动
springboot应用,可以在启动时再通过
-Dspring.profiles.active参数切换配置,而本例使用环境变量主要是解决将应用构建为
Docker镜像时,无法在启动时再通过
-Dspring.profiles.active参数切换配置的问题。
spring.profiles.include导入自定义文件有一个强制约定,文件名必须以
application-开头。
application-作为文件名前缀的情况下,并且想让
SpringBoot能够根据环境选择是否加载
resources目录下的自定义配置文件时,就无法使用
spring.profiles.include。
SpringBoot加载配置文件的了解,相信你已经有了答案。没错,可是通过自定义
EnvironmentPostProcessor实现。
common-开头,例如:
common-rds、
common-redis。如果是线上环境直接从配置中心读取,只在本地测试不想从配置中心读取的情况下,自定义的
EnvironmentPostProcessor才会加载自定义配置文件。
EnvironmentPostProcessor加载自定义配置文件,导入配置信息,整体上只需要两步:
ProfileEnvironmentPostProcessor实现
EnvironmentPostProcessor接口,代码如下。
public class ProfileEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
@Override
public 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<PropertySource<?>> 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容器的环境(前者最终变为后者的父容器),下面是笔者的实现,仅供参考。

通过在
bootstrap.yaml
配置文件中配置spring.cloud.config.choose
指定当前应用需要导入哪些配置文件。当spring.profiles.active
配置为dev
时才去加载spring.cloud.config.choose
指定的配置文件。
由于
Spring Cloud
启动的容器与Spring Boot
启动的容器使用的不是同一个ProfileEnvironmentPostProcessor
对象,但使用的是同一个类加载器加载的类,因此可以通过静态变量共享spring.cloud.config.choose
配置。

文章转载自Java艺术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




