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

news2024/9/24 7:16:17

文章目录

  • 一、介绍
  • 二、对contributors中的配置属性进行处理
  • 三、处理`contributors`中的配置属性
    • 1. 解析配置文件位置和资源
    • 2. 加载配置属性
  • 四、确定当前运行环境激活的profile
    • 1. 获取附加的addtionalProfiles
    • 2. 获取spring.profiles.include定义的配置
    • 3. 获取spring.profiles.active定义的配置
  • 五、将contributors中保存的配置信息应用到当前运行环境中
  • 六、总结

一、介绍

上一篇文章: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对象集合中的配置属性再应用到运行环境中。

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



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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/810731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

互斥量 的初识

Q: 什么是互斥量&#xff1f; A: 在多数情况下&#xff0c;互斥型信号量和二值型信号量非常相似&#xff0c;但是从功能上二值型信号量用于同步&#xff0c; 而互斥型信号量用于资源保护。 互斥型信号量和二值型信号量还有一个最大的区别&#xff0c;互斥型信号量可以有效解决…

CAD .NET 15.0 企业版 Crack

CAD .NET 15.0 企业版 企业版 企业版 企业版 企业版 Updated: June 14, 2023 | Version 15.0 NEW CAD .NET is a library for developing solutions in .NET environment. It supports AutoCAD DWG/ DXF, PLT and other CAD formats. The library can be used in a wide rang…

SpringBoot百货超市商城系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBoot框架开发的百货超市系统。首先&#xff0c;这是一个很适合SpringBoot初学者学习的项目&#xff0c;代…

在linux上面部署activemq

1、下载 网址&#xff1a;ActiveMQ 注意&#xff1a;新版本5.17起 要求jdk11, 5.16兼容jdk8, 所以&#xff0c;确保已经安装 java11 或以上的版本 这里安装较新版&#xff1a;5.18.2&#xff0c;已经安装了java17 如何安装jdk17,请详见我的另一篇文章&#xff1a;linux…

mermaid使用记录

记录mermaid可使用到场景&#xff0c;部分关键使用过程备忘 markdown idea开启mermaid预览 grafana 参考插件 Diagram https://grafana.com/grafana/plugins/jdbranham-diagram-panel/ 结合监控数据&#xff0c;可以展示某个处理流程中&#xff0c;各个中间环节的处理指标及…

巧用NGINX配置解决跨域问题

页面nginx配置 1&#xff0c;前端页面放在域名根目录&#xff0c;比如&#xff0c;http://www.xuecheng.com/ &#xff0c;对应的nginx配置&#xff1a; #门户location / {alias D:/Z_lhy/SpringCloud/xuecheng_online/www/xc-ui-pc-static-portal/;index index.html;} 页…

VMPWN的入门系列-2

温馨提示&#xff1a; 文章有点长&#xff0c;图片比较多&#xff0c;请耐心阅读 实验四 VMPWN4 题目简介 这道题应该算是虚拟机保护的一个变种&#xff0c;是一个解释器类型的程序&#xff0c;何为解释器&#xff1f;解释器是一种计算机程序&#xff0c;用于解释和执行源代码。…

quarkus核心编程笔记

此篇只做总结&#xff0c;有大佬做的更详细 大佬quarkus笔记 依赖注入 在应用中&#xff0c;一个接口有多个实现是很常见的&#xff0c;那么依赖注入时&#xff0c;如果类型是接口&#xff0c;如何准确选择实现呢&#xff1f; 修饰符匹配Named注解属性匹配根据优先级选择写…

小红书推广 方法总结

大家好&#xff0c;我是网媒智星&#xff0c;今天跟大家分享一下小红书的推广方法和经验。 一、平台简介 1、什么是小红书&#xff1f; 小红书是一个消费决策/生活方式平台&#xff0c;用户可以通过图片、文案、视频等方式分享美好生活。 2、用户画像 - 2亿月活跃…

better scoll的使用以及注意事项以及左联右

下载better scoll的核心 在你要使用的页面引入 在data里面定义一个对象 然后在createad里面放一个nexttick异步操作。 上面是获取这个left-box节点是父节点 记住里面只能有一个子节点如果循环了 就要再包一个div就是一个子节点 左联右 首先也要获取 右边的 父节点 然后配…

RPC与REST有什么区别?

背景 好多开发的同学在工作中&#xff0c;经常分不清RPC和REST的区别&#xff0c;导致经常沟通不在一个层次上。甚至有些同学把这两个当成同一个东西。 RPC与REST的区别&#xff1f; 对比名称rpcrest备注架构风格RPC是基于过程调用的架构风格&#xff0c;它将远程方法调用封装为…

深度学习技巧应用24-深度学习手撕代码与训练流程的联系记忆方法

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用24-深度学习手撕代码与训练流程的联系记忆方法,大家都知道深度学习模型训练过程是个复杂的过程,这个过程包括数据的收集,数据的处理,模型的搭建,优化器的选择,损失函数的选择,模型训练,模型评估等步骤,其中缺少…

gitee使用参考

Git代码托管服务 2.1 常用的Git代码托管服务 gitHub&#xff08; 地址&#xff1a;https://github.com/ &#xff09;是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持Git 作为唯一的版本库格式进行托管&#xff0c;故名gitHub码云&#xff08;地址&#xff1a;…

shell脚本:数据库的分库分表

#!/bin/bash ######################### #File name:db_fen.sh #Version:v1.0 #Email:admintest.com #Created time:2023-07-29 09:18:52 #Description: ########################## MySQL连接信息 db_user"root" db_password"RedHat123" db_cmd"-u${…

ROS暑期学校分享-2023

云课的优势 https://gitcode.net/ZhangRelay/cocubesim 网络编程和单机编程 网络编程和单机编程是两种不同的编程方式&#xff0c;它们的主要区别在于其应用场景和实现技术上。 1 应用场景 网络编程主要用于构建基于互联网的应用程序&#xff0c;例如Web应用程序、网上购物…

安装typora

1、下载压缩包 链接&#xff1a;https://pan.baidu.com/s/1nFvk3hAyXNbvKPJnu9ipIA 提取码&#xff1a;sdyy 2、安装typora 3、打开Crack 4、将这个dll文件复制粘贴到typora的安装路径里

Linux--进程的新建状态

新建状态&#xff1a; 操作系统创建了进程的内核数据结构&#xff08;task_struct、mm_struct、页表&#xff09;&#xff0c;但是页表没有创建映射关系&#xff0c;而且磁盘里的程序的代码和数据未加载到物理内存

Spring注解系列——@PropertySource

在Spring框架中PropertySource注解是非常常用的一个注解&#xff0c;其主要作用是将外部化配置解析成key-value键值对"存入"Spring容器的Environment环境中&#xff0c;以便在Spring应用中可以通过Value或者占位符${key}的形式来使用这些配置。 使用案列 // Propert…

React 路由使用-详细介绍

路由初使用 抽象路由模块 src\page\Article\index.js const Article () > {return (<div><p>文章页</p></div>); };export default Article;src\router\index.js // 导入页面 import Article from "../page/Article"; import Login fr…

Leetcode 剑指 Offer II 037. 小行星碰撞

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个整数数组 asteroids&#xff0c;表示在同一行的小行星。…