首页 > 编程知识 正文

spring加载配置文件,spring配置文件加载顺序

时间:2023-05-05 09:13:01 阅读:198622 作者:3300

关注 “Java艺术” 我们一起成长!

你好,我是大气的毛衣,

这是我的第170篇原创文章,欢迎阅读!

或许你也发现了,在配置项多的情况下,application-xx.yml配置文件显得过于臃肿,并且在一个分布式项目中,数据库、redis等配置通常是每个微服务都会用到的配置,也都是相同的配置。

为了解决单一配置文件过于臃肿的问题,并且实现让多个微服务共用一些配置文件,我们在新项目中将以往的单配置文件拆分成了多个配置文件。

另外,我们使用kubernetes的ConfigMap资源作为“配置中心”,可以为每个配置文件创建一个ConfigMap资源,每个微服务项目需要哪些配置文件就可以只引用哪些ConfigMap资源。spring-cloud-kubernete-config会自动读取引用的ConfigMap资源中的配置信息,并写入到Environment中。

虽然通过配置中心加载配置可以去掉配置文件,但本地测试我们通常不会通过配置中心去读取,因此,将单一配置文件拆分为多个配置文件之后,本地测试如何让SpringBoot加载这些配置文件就是我们要解决的问题。

本篇将介绍两种加载自定义配置文件的实现方式,并通过分析源码了解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导入

实现加载自定义配置文件最简单的方式,我们可以通过配置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参数切换配置的问题。

通过自定义EnvironmentPostProcessor导入

通过配置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艺术] 微信号:javaskill

一个只推送原创文章的技术公众号,分享Java后端相关技术。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。