springboot创建并配置环境(四) - 配置扩展属性(下集)

29 篇文章 2 订阅
10 篇文章 0 订阅

一、介绍

上一篇文章:springboot创建并配置环境(三) - 配置扩展属性(上集)中我们介绍了springboot对配置文件的处理逻辑,但是由于篇幅过长,决定分上下集两部分讲解。

二、对contributors中的配置属性进行处理

上集对processAndApply()方法的分析中,概括来讲就是分四步:①在确定profiles前处理contributors中的配置属性。②确定profiles。③在确定profiles后处理contributors中的配置属性。④将contributors中的配置属性应用到当前运行环境Environment中。

因此下面我们来分析以下逻辑

  • 处理contributors中的配置属性
  • 确定profiles
  • 将配置属性应用到当前运行环境Environment中。

三、处理contributors中的配置属性

该逻辑通过三个方法完成,分别是processInitial()processWithoutProfiles()processWithProfiles()。而这三个方法其实内部实现都是通过调用contributors对象的withProcessedImports()方法完成的,他们之间的区别就是是否传入确定的profiles

在这里插入图片描述

因此我们主要对withProcessedImports()方法进行分析。

先来看一下该方法的源码如下:

ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
			ConfigDataActivationContext activationContext) {
    // 根据环境激活上下文获取导入阶段,所谓导入阶段为profiles激活前和profiles激活后两个阶段
    ImportPhase importPhase = ImportPhase.get(activationContext);
    this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
                                        (activationContext != null) ? activationContext : "no activation context"));
    // this表示contributors表示的对象,将该对象赋值给result,本质上仍然是保存contributor集合的contributors对象
    ConfigDataEnvironmentContributors result = this;
    int processed = 0;
    while (true) {
        // 从contributor集合中获取下一个将要处理的contributor
        ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
        if (contributor == null) {
            // 如果contributor集合中没有要处理的元素,则返回该集合
            this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
            return result;
        }
        if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
            // 如果contributor的类型为UNBOUND_IMPORT(未绑定导入)
            
            // 从当前正处理的contributor对象中获取ConfigurationPropertySource
            Iterable<ConfigurationPropertySource> sources = Collections
                .singleton(contributor.getConfigurationPropertySource());
            // 创建placeholder解析器,用来解析${}
            PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
                result, activationContext, true);
            // 创建binder对象,binder中包含了ConfigurationPropertySource解析器
            Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
            // 将当前contributor复制给一个新的对象,并将类型修改为BOUND_IMPORT(已绑定导入)
            ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
            // 根据原contributor集合重新创建一个Contributors对象,并将当前正处理的contributor对象进行替换
            result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
                                                           result.getRoot().withReplacement(contributor, bound));
            continue;
        }
        // 创建位置解析器上下文
        ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
            result, contributor, activationContext);
        // 创建配置数据加载器上下文
        ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
        // 从contributor中获取imports,imports中包含了当前contributor对象要处理的配置文件路径
        List<ConfigDataLocation> imports = contributor.getImports();
        this.logger.trace(LogMessage.format("Processing imports %s", imports));
        // 调用importer的resolveAndLoad()方法来解析并读取配置数据
        // 在importer中已经包含了位置解析器、配置数据加载器。
        // 返回值是一个map对象,其中key中包含了配置文件的路径及其资源,value中包含的是配置文件中的配置数据
        Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
                                                                              			locationResolverContext, 
                                                                                       loaderContext, 
                                                                                       imports);
        this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
        // 将读取到的配置通过父子关系,设置为当前正处理的contributor的children,
        // asContributors()方法将读取到的配置封装成contributor对象并设置其类型为UNBOUND_IMPORT(未绑定导入)
        ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
                                                                                           asContributors(imported));
        // 根据原contributor集合重新创建一个Contributors对象,并将当前正处理的contributor对象进行替换
        result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
                                                       result.getRoot().withReplacement(contributor, contributorAndChildren));
        processed++;
    }
}

其中,在contributor类型为UNBOUND_IMPORT的if代码块中,主要是把该contributor的类型修改为BOUND_IMPORT,并作为一个新的contributor对象将原contributor对象进行替换。如下所示

在这里插入图片描述

下面,我们分析withProcessedImports()方法剩余部分逻辑:

在这里插入图片描述

其中,我们先进入importer.resolveAndLoad()方法,该方法返回一个map对象(key为配置文件资源对象,value为从配置文件资源中加载的配置属性),其内部逻辑分两部分:①解析配置文件位置和资源,②加载配置属性

在这里插入图片描述

1. 解析配置文件位置和资源

我们进入resolve()方法查看如何解析配置文件的位置和资源。

在这里插入图片描述

从上面源码中可以看到,resolve()方法对其locations参数(配置文件位置)进行遍历,对每一个配置文件位置再调用重载的resolve()方法进行解析。真正的解析过程是通过调用配置文件位置解析器resolversresolve()方法实现的。

在这里插入图片描述

前面讲过,springboot提供了两个配置文件解析器:①ConfigTreeConfigDataLocationResolver;②StandardConfigDataLocationResolver。我们从这两个解析器的isResolvable()方法便可以判断出区别:前者用于解析带有前缀configtree:的配置文件路径;后者解析任意配置文件路径。

下面我们以StandardConfigDataLocationResolver为例,分析如何解析配置文件位置。

其中resolve()方法和resolveProfileSpecific()方法逻辑大致相同,只是后者携带有效的profile参数

因此我们分析其resolve()方法,该方法先获取配置文件资源的引用再根据该文件引用,获取该文件资源

在这里插入图片描述

下面我们看如何获取配置文件资源的引用,以目录为例,查看getReferencesForDirectory()方法

在这里插入图片描述

进一步查看配置文件引用的构造方法

在这里插入图片描述

由此我们便可以知道,springboot是如何根据spring.profiles.active属性确定profiles对应的配置文件资源

当我们得到配置文件资源的引用后,通过该引用获取对应的配置文件资源

在这里插入图片描述

我们再回到配置文件位置解析器resolversresolve()方法。

在这里插入图片描述

下面我们回到resolveAndLoad()方法,其中resolved集合中包含了默认指定的以及指定profile对应的配置文件资源。然后在调用load()方法,从配置文件资源中加载配置属性即可。

在这里插入图片描述

2. 加载配置属性

这里我们关注resolveAndLoad()方法对load()方法的调用。

该方法以配置文件资源分析结果集合为参数,返回一个Map集合,其中key为配置文件资源分析结果,value为配置文件资源中的配置属性。

在这里插入图片描述

进入加载器loadersload()方法,该方法用于加载指定配置文件资源中并返回该配置文件中的配置属性

在这里插入图片描述

从该方法中看到,配置属性加载器有两种,分别是ConfigTreeConfigDataLoaderStandardConfigDataLoader,还记得前面我们分析的配置文件位置解析器也有两个分别是ConfigTreeConfigDataLocationResolverStandardConfigDataLocationResolver,他们是一一对应的。

我们以StandardConfigDataLocationResolver为例,查看它的load()方法。

在这里插入图片描述

从该方法中我们看到,对配置文件中配置属性的加载是通过配置属性加载器中的load()方法实现的,而该加载器又分为propertiesyaml两种。

load()方法将我们在配置文件中定义的配置属性进行加载,并转化为propertySource集合。再将该集合封装到ConfigData对象中并返回。

而该加载器中对配置文件资源中的配置属性的加载过程我们这里就不做分析了,请有兴趣的读者自查。

最后再回到withProcessedImports()方法

在这里插入图片描述

resolveAndLoad()方法我们就分析结束了,该方法返回的imported对象为map集合,其中key为配置文件资源分析结果,value为配置文件资源中的配置属性。最后通过withChildren()方法将该map集合转为contributor对象并保存到children属性中,再通过withReplacement()方法将contributor对象更新

至此我们在配置文件中定义的所有配置属性均已保存到contributors对象中并返回。此时contributors对象的结构如下

在这里插入图片描述

四、确定当前运行环境激活的profile

此过程由processAndApply()方法中调用withProfiles()方法完成

在这里插入图片描述

下面我们进入该方法源码查看

在这里插入图片描述

1. 获取附加的addtionalProfiles

从源码上看,addtionalProfiles属性是在ConfigDataEnvironment类的成员变量中直接定义的,且该属性是通过该类的构造方法设置的

class ConfigDataEnvironment {
    // ...
	private final Collection<String> additionalProfiles;    
    // ...
    
    ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
			ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
		// ...
		this.additionalProfiles = additionalProfiles;
		// ...
	}
}

通过该构造方法的调用链我们可以发现,该构造方法的调用如下所示

在这里插入图片描述

由此可知,附加profiles是从SpringApplication类中获取的,那么是否也是由SpringApplication类设置的呢?答案是肯定的。

SpringApplication类中有对应的方法定义

在这里插入图片描述

因此我们可以在springboot的主启动方法中通过以下方式设置

在这里插入图片描述

2. 获取spring.profiles.include定义的配置

进入getIncludedProfiles()方法

在这里插入图片描述

从该方法中看到,对contributors中的contributor进行遍历,从中获取key为spring.profiles.include的配置属性,将其添加到集合中并返回。

3. 获取spring.profiles.active定义的配置

进入Profiles的构造方法查看,

在这里插入图片描述

该构造方法中定义了三种profiles,分别是spring.profiles.group定义的profiles、spring.profiles.active定义的profiles和spring.profiles.default定义的profiles。我们逐个查看

  • spring.profiles.group定义的profiles

    Profiles的构造方法得知,springboot通过spring.profiles.group定义profiles分组,且定义方式为Map集合。我们通过下面示例说明

    在这里插入图片描述

    调式源码

    在这里插入图片描述

  • spring.profiles.active定义的profiles

    Profiles的构造方法得知,springboot通过getActivatedProfiles()方法获取spring.profiles.active定义的profiles

    在这里插入图片描述

    getActivatedProfiles()方法中,springboot获取spring.profiles.active定义的profiles,并将前面获取的additionalProfiles一同添加到集合中并返回,作为Profiles实例的activeProfiles属性。

  • spring.profiles.default定义的profiles

    Profiles的构造方法得知,springboot通过getDefaultProfiles()方法获取spring.profiles.default定义的profiles

    在这里插入图片描述

    getDefaultProfiles()方法中,springboot获取spring.profiles.default定义的profiles(默认为default),并将其添加到集合中并返回,作为Profiles实例的defaultProfiles属性。

最后,在将所有定义的profiles封装到Profiles实例后,通过activationContext.withProfiles()方法将Profiles实例添加到profiles激活上下文中。

在这里插入图片描述

五、将contributors中保存的配置信息应用到当前运行环境中

下面我们便到达processAndApply()方法的最后一步,将contributors中保存的配置信息应用到当前运行环境

在这里插入图片描述

下面进入applyToEnvironment方法的源码

在这里插入图片描述

从源码可见,该方法虽然较长,但逻辑比较简单,就是将contributor集合中来自配置文件的配置属性添加到当前运行环境的配置属性集合中,然后对当前运行环境设置profiles。

此时,运行环境中的所有配置属性均已设置完毕,包含来自系统的配置属性以及来自配置文件的配置属性等,如下所示

在这里插入图片描述



至此,我们对springboot创建并配置运行环境的整个过程就分析结束了,其过程虽然繁琐,但如果认真梳理,其处理逻辑并不复杂,只需我们在阅读源码时耐得住寂寞沉得住气即可。

六、总结

  • 在启动环境中主要保存配置信息和当前操作系统的配置信息以及环境变量
  • 针对不同的应用程序类型,springboot创建对应的运行环境实例,如StandardEnvironmentStandardServletEnvironmentStandardReactiveWebEnvironment
  • 在创建运行环境实例时,其构造器内部就已经首先将系统属性环境变量保存到其内部属性中了。
  • 通过观察者模式发布环境准备就绪事件,由监听该事件的各种监听器针对该事件进行不同的逻辑处理。
  • 涉及到的设计模式
    • 观察者模式:发布环境准备就绪事件,由对应的监听器执行逻辑
    • 工厂模式:环境后处理器工厂
  • 通过contributor对象临时保存所有配置文件中的配置属性
  • 配置文件的格式有多种,propertiesxmlyaml、以及yml
  • 配置文件的位置有多种,classpath:/classpath:/config/file:./file:./config/file:./config/*/、以及指定的位置spring.config.location
  • 最后将contributor对象集合中的配置属性再应用到运行环境中。

点此进入上一集:配置扩展属性 - 上



纸上得来终觉浅,绝知此事要躬行。

————————我是万万岁,我们下期再见————————

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

理想万岁万万岁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值