【详细学习SpringBoot源码之属性配置文件加载原理(application.properties|application.yaml)-7】

news2024/11/19 13:36:26

一.知识回顾

【0.SpringBoot专栏的相关文章都在这里哟,后续更多的文章内容可以点击查看】
【1.SpringBoot初识之Spring注解发展流程以及常用的Spring和SpringBoot注解】
【2.SpringBoot自动装配之SPI机制&SPI案例实操学习&SPI机制核心源码学习】
【3.详细学习SpringBoot自动装配原理分析之核心流程初解析-1】
【4.详细学习SpringBoot自动装配原理之自定义手写Starter案例实操实战-2】
【5.IDEA中集成SpringBoot源码环境详细步骤讲解】
【6.初识SpringBoot核心源码之SpringApplication构造器以及run方法主线流程-3】
【7.详细学习SpringBoot核心源码之SpringApplication构造器&Run方法源码详细流程-4】
【8.详细学习SpringBoot核心源码之监听器原理-5(观察者设计模式、初始化并加载监听器核心流程、事件的发布器核心流程、SpringBoot中默认的监听器以及默认的事件类型)】
【9.详细学习SpringBoot源码之自定义监听器实战演练-6(自定义监听器、自定义监听事件、指定监听事件)】

二.SpringBoot源码之属性文件加载原理(application.properties|application.yaml)

2.1 属性配置文件加载原理分析

在创建SpringBoot项目的时候会在对应的application.properties或者application.yml文件中添加对应的属性信息,我们的问题是这些属性文件是什么时候被加载的?如果要实现自定义的属性文件怎么来实现呢?

在这里插入图片描述

2.2 属性配置文件加载原理分析入口

结合我们前面介绍的SpringBoot中的监听事件机制,我们首先看下SpringApplication.run()方法,在该方法中会针对SpringBoot项目启动的不同的阶段来发布对应的事件。

在这里插入图片描述

application.properties配置文件加载的过程代码如下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		// 在配置环境信息之前发布事件
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

处理属性文件加载解析的监听器是 ConfigFileApplicationListener ,这个监听器监听的事件有两个。

在这里插入图片描述

  而我们进入SpringApplication.prepareEnvironment()方法中发布的事件其实就是ApplicationEnvironmentPreparedEvent事件。
在这里插入图片描述

进入方法listeners.environmentPrepared(environment);

在这里插入图片描述

进入定义listener.environmentPrepared(environment);接口的方法
在这里插入图片描述

继续进入会看到对应的发布事件:ApplicationEnvironmentPreparedEvent

在这里插入图片描述

在此处打一个断点,通过debug运行结果:在initialMulticaster中是有ConfigFileApplicationListener这个监听器的。如果再这个位置触发了配置环境的监听器,后续的逻辑就应该进入对应的逻辑实现方法。

image.png

三.SpringBoot源码之属性配置文件监听器-ConfigFileApplicationListener监听流程分析

3.1 ConfigFileApplicationListener监听流程分析

ConfigFileApplicationListener中具体的如何来处理配置文件的加载解析的。

在这里插入图片描述

如果是 ApplicationEnvironmentPreparedEvent则进入onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);方法
在这里插入图片描述

直接进入ConfigFileApplicationListener.postProcessEnvironment()方法。
在这里插入图片描述
进入实现类重写的方法
在这里插入图片描述

在进入addPropertySources()方法中会完成两个核心操作:

  1. 创建Loader对象
  2. 调用Loader对象的load方法
/**
	 * Add config file property sources to the specified environment.
	 * @param environment the environment to add source to
	 * @param resourceLoader the resource loader
	 * @see #addPostProcessors(ConfigurableApplicationContext)
	 */
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		// 创建Loader对象同时会完成属性加载器的加载 同时调用load方法
		new Loader(environment, resourceLoader).load();
	}

3.2 Loader构造器

在Loader构造器中执行了什么操作,最重要的就是加载解析配置文件。

在这里插入图片描述

通过源码我们可以发现在其中获取到了属性文件的加载器、从spring.factories文件中获取,对应的类型是 PropertySourceLoader类型。

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
# 加载Properties配置文件
org.springframework.boot.env.PropertiesPropertySourceLoader,\
# 加载Yaml配置文件
org.springframework.boot.env.YamlPropertySourceLoader

在这里插入图片描述

并在loadFactories方法中会完成对象的实例化。

在这里插入图片描述

3.3 调用Loader对象的load方法

接下来学习load()方法的执行

void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						// 创建默认的profile 链表
						this.profiles = new LinkedList<>();
						// 创建已经处理过的profile 类别
						this.processedProfiles = new LinkedList<>();
						// 默认设置为未激活
						this.activatedProfiles = false;
						// 创建loaded对象
						this.loaded = new LinkedHashMap<>();
						// 加载配置 profile 的信息,默认为 default
						initializeProfiles();
						// 遍历 Profiles,并加载解析
						while (!this.profiles.isEmpty()) {
							// 从双向链表中获取一个profile对象
							Profile profile = this.profiles.poll();
							// 非默认的就加入,进去看源码即可清楚
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
						// 解析 profile
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
						// 加载默认的属性文件 application.properties
						addLoadedPropertySources();
						applyActiveProfiles(defaultProperties);
					});
		}

然后进入具体的apply()方法

static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
			Consumer<PropertySource<?>> operation) {
		// 获取当前环境下的所有的资源加载器
		MutablePropertySources propertySources = environment.getPropertySources();
		// 根据propertySourceName从众多的加载器中获取对应的加载器 默认的没有
		PropertySource<?> original = propertySources.get(propertySourceName);
		if (original == null) {
			operation.accept(null);
			return;
		}
		propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
		try {
			operation.accept(original);
		}
		finally {
			propertySources.replace(propertySourceName, original);
		}
	}

退出来,再次学习load主方法流程,然后进入load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			// 获得默认的扫描路径,如果没有特殊指定 ,
			// 就采用常量 DEFAULT_ SEARCH_ LOCATIONS中定义的4个路 径 。
			// 而getSearchNames 方 法获得的就是 application 这个默认的配置文件名。
			// 然后,逐一遍历加载目录路径及其指定文件名的文件。
			// file:./config/ file:./ classpath:/config/ classpath:/ 默认的四个路径
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
				// 去对应的路径下获取属性文件 默认的文件名称为 application
				Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

首先是getSearchLocations()方法,在该方法中会查询默认的会存放对应的配置文件的位置,如果没有自定义的话,路径就是 file:./config/ file:./ classpath:/config/ classpath:/ 这4个

private Set<String> getSearchLocations() {
			// 如果有自定义的路径就使用自定义的
			if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
				return getSearchLocations(CONFIG_LOCATION_PROPERTY);
			}
			// 否则使用默认的4个路径 file:./config/ file:./ classpath:/config/ classpath:/
			Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
			locations.addAll(
					asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
			return locations;
		}

进入getSearchLocations(String propertyName) 方法

private Set<String> getSearchLocations(String propertyName) {
			Set<String> locations = new LinkedHashSet<>();
			if (this.environment.containsProperty(propertyName)) {
				for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) {
					if (!path.contains("$")) {
						path = StringUtils.cleanPath(path);
						if (!ResourceUtils.isUrl(path)) {
							path = ResourceUtils.FILE_URL_PREFIX + path;
						}
					}
					locations.add(path);
				}
			}
			return locations;
		}

然后回到load方法中,遍历4个路径,然后加载对应的属性文件。

在这里插入图片描述

  getSearchNames()获取的是属性文件的名称。如果自定义了就加载自定义的

image.png

  否则加载默认的application文件。

image.png

再回到前面的方法

image.png

进入load方法,会通过前面的两个加载器来分别加载application.properties和application.yml的文件。

image.png
具体实现的代码

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
			if (!StringUtils.hasText(name)) {
				for (PropertySourceLoader loader : this.propertySourceLoaders) {
					if (canLoadFileExtension(loader, location)) {
						// 去对应的文件夹下加载属性文件 applicaiton.properties application.yml
						load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
						return;
					}
				}
				throw new IllegalStateException("File extension of config file location '" + location
						+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
						+ "a directory, it must end in '/'");
			}
			Set<String> processed = new HashSet<>();
			// 获取properties和yml的资源加载器
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				// 获取对应的加载器加载的后缀 properties xml yml ymal
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
						// 加载文件 application.properties application.xml
						// application.yml application.yaml
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
								consumer);
					}
				}
			}
		}

loader.getFileExtensions()获取对应的加载的文件的后缀。
在这里插入图片描述

image.png

image.png
然后退出进入loadForFileExtension方法
image.png

进入loadForFileExtension()方法,对profile和普通配置分别加载

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			if (profile != null) {
				// 如果有profile的情况比如 dev --> application-dev.properties
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
			// 加载正常的情况的属性文件 application.properties
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

继续进入 load(loader, prefix + fileExtension, profile, profileFilter, consumer); 方法

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
			try {
				Resource resource = this.resourceLoader.getResource(location);
				if (resource == null || !resource.exists()) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped missing config ", location, resource,
								profile);
						this.logger.trace(description);
					}
					return;
				}
				if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped empty config extension ", location,
								resource, profile);
						this.logger.trace(description);
					}
					return;
				}
				String name = "applicationConfig: [" + location + "]";
				// 加载属性文件信息
				List<Document> documents = loadDocuments(loader, name, resource);
				if (CollectionUtils.isEmpty(documents)) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
								profile);
						this.logger.trace(description);
					}
					return;
				}
				List<Document> loaded = new ArrayList<>();
				for (Document document : documents) {
					if (filter.match(document)) {
						addActiveProfiles(document.getActiveProfiles());
						addIncludedProfiles(document.getIncludeProfiles());
						loaded.add(document);
					}
				}
				Collections.reverse(loaded);
				if (!loaded.isEmpty()) {
					loaded.forEach((document) -> consumer.accept(profile, document));
					if (this.logger.isDebugEnabled()) {
						StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
						this.logger.debug(description);
					}
				}
			}
			catch (Exception ex) {
				throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
			}
		}

image.png

image.png

image.png

image.png

image.png

接下来我们就要开始加载我们存在的application.properties文件。

3.4 资源加载器是加载具体的文件信息(application.properties为例)

在找到了要加载的文件的名称和路径后,我们来看下资源加载器是如何来加载具体的文件信息的。

在这里插入图片描述

进入loadDocuments方法中,我们会发现会先从缓存中查找,如果缓存中没有则会通过对应的资源加载器来加载了。

image.png

此处是PropertiesPropertySourceLoader来加载的。当然如果我们使用的是yml配置文件的话,使用的就是YamlPropertySourceLoader。

image.png

image.png

进入loadProperties方法

image.png

之后进入load()方法看到的就是具体的加载解析properties文件中的内容了。
image.png

好了,关于【详细学习SpringBoot源码之属性配置文件加载原理(application.properties|application.yaml)-7】就先学习到这里,更多的内容持续学习创作中,敬请期待。

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

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

相关文章

教室管理系统

开发工具(eclipse/idea/vscode等)&#xff1a;idea 数据库(sqlite/mysql/sqlserver等)&#xff1a;mysql 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 关于这个系统的具体功能主要包括教师&#xff0c;学生&#xff0c;课程&#xff0c;教室还有班级这几个实体。…

acwing基础课——二分图

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板3——搜索与图论 - AcWing 基本思想&#xff1a; 二分图:在一张图中&#xff0c;如果能把全部点分到两个集合&#xff0c;且保证两个集合内部没有任何一条边&#xff0c;图中的边只存在于两个集合之间&#xff0c…

制造业ERP如何做好成本核算管理?

随着制造业的不断发展&#xff0c;制造业成本管理中存在的问题已成为制造业企业关注的焦点。在传统粗放的手工模式下&#xff0c;制造企业成本核算工作量会非常巨大&#xff0c;不能对成本信息进行实时监控&#xff0c;只能在成本费用发生后进行归集核算&#xff0c;数据有滞后…

PS CS6视频剪辑基本技巧(四)字幕居中和滚动字幕

在第三讲中介绍了添加字幕的方法&#xff0c;但有的读者可能会发现&#xff0c;字幕模板设定的字幕起始是固定不变的&#xff0c;假如设定的起始位置是最左边&#xff0c;那么无论一行字多有多少个&#xff0c;都是从最左边开始排。那么有没有办法可以让字幕可以批量居中呢&…

大数据技术之SparkCore

文章开篇先简单介绍一下SparkCore&#xff1a; Spark Core是spark的核心与基础&#xff0c;实现了Spark的基本功能&#xff0c;包含任务调度&#xff0c;内存管理&#xff0c;错误恢复与存储系统交互等模块 Spark Core中包含了对Spark核心API——RDD API(弹性分布式数据集)的定…

你以为传切片就是传引用了吗?

xdm &#xff0c;我们在写 golang 的时候&#xff0c;引用和传值傻傻分不清&#xff0c;就例如我们传 切片 的时候&#xff0c;你能分清楚你传的切片是传值还是传引用呢&#xff1f; 引用是什么&#xff1f; 引用就是给对象起另一个名字&#xff0c;引用类型引用另一种类型 引…

【自省】线程池里的定时任务跑的可欢了,可咋停掉特定的任务?

客户端抢到分布式锁之后开始执行任务&#xff0c;执行完毕后再释放分布式锁。持锁后因客户端异常未能把锁释放&#xff0c;会导致锁成为永恒锁。为了避免这种情况&#xff0c;在创建锁的时候给锁指定一个过期时间。到期之后锁会被自动删除掉&#xff0c;这个角度看是对锁资源的…

Going Home(二分图最大权匹配KM算法)

C-Going Home_2022图论班第一章图匹配例题与习题 (nowcoder.com) 在网格地图上有n个小人和n座房子。在每个单位时间内&#xff0c;每个小人都可以水平或垂直地移动一个单位步到相邻点。对于每个小矮人&#xff0c;你需要为他每走一步支付1美元的旅费&#xff0c;直到他进入一所…

Git命令笔记,下载、提交代码、解决冲突、分支处理

下载代码&#xff0c;复制https地址到本地文件夹&#xff0c;鼠标右键选择git bash后输入命令 git clone https://gitee.com/View12138/ViewFaceCore.git 下载后初始化&#xff1a;git init 下载代码后不运行报错&#xff08;如下&#xff09;&#xff0c;需要执行初始化命令…

Google ProtoBuf的使用

Google的protobuf太好用了&#xff0c;又小&#xff0c;读写又快 跑步快慢受鞋的影响太大了&#xff0c;但是造鞋的工具研究起来还是很有难度的&#xff0c;百度真是充斥的大量的转载文件&#xff0c;不管能不能用、能不能看懂&#xff0c;反正是各种转载&#xff0c;有的连错…

2023年企业固定资产管理怎么破局?

2022年已经在风雨中过去&#xff0c;转眼我们迎来了2023年。过去的一年&#xff0c;固定资产管理的痛依旧历历在目&#xff0c;如何让新的一年中&#xff0c;固定资产管理工作有所突破&#xff0c;不再承受固定资产资产管理的痛处&#xff0c;是每个企业管理者和企业固定资产管…

snap打包初步了解

前言 和snap比较类似的有三种打包方式&#xff1a; Snap Flatpak appimage Appimage是将所有的资源打包在一起&#xff0c;以一个类似与独立exe的方式执行&#xff0c;虽然简单使用&#xff0c;但是解压资源和本地缓存数据都比较麻烦。 Flatpak和snap十分类似&#xff0c;但…

XXE无回显攻击详解

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是XXE无回显攻击详解。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁对未授权设…

怎么把element的tootip设置为点击后出现提示框,且在提示框里面放其他元素,vue2动态给对象添加属性并实现响应式应答,样式穿透

怎么把element的tootip设置为点击后出现提示框 我目前有一个需求&#xff0c;就是要点击文字才会出现提示框&#xff0c;而不是hover上去就以后&#xff0c;找资料看文档&#xff0c;看了半天让我终于实现了&#xff0c;其实也不难&#xff0c;可能是最开始我没有理解value&am…

Akka 进阶(一)Dispatcher调度器

目录一 Dispatcher 任务分发1.1 理解什么是Dispatcher1.2 Executor的分类1.3 基本使用1.4 其他类型的调度器在Akka中&#xff0c;Actor的消息通信和任务执行建立在一个完全透明的调度机制之上&#xff0c;它屏蔽了底层线程&#xff08;池&#xff09;的实现细节&#xff0c;几乎…

Java: static,final,代码块 的详解

Java: static&#xff0c;final&#xff0c;代码块 的详解 每博一文案 山本文绪说过这样一句话&#xff1a;哪些决定放弃了的事&#xff0c;就请放弃得干干净净。哪些决定再也不见面的人&#xff0c;就真 的不要再见面了&#xff0c;不要再做背叛自己的事&#xff0c;如果想要…

CentOS7迷你版安装Redis并配置基础信息

1. 安装gcc、wget依赖 yum install gcc yum install wget 2. 使用命令&#xff1a;wget http://download.redis.io/releases/redis-6.2.5.tar.gz 下载安装包&#xff0c;注意要先cd到要下载到的目标位置&#xff09; 3. tar -zxvf redis-6.2.5.tar.gz 解压压缩包 4. cd redis-…

学习周报-20221223

文章目录一 Linux的ACL访问控制列表一 基础概念1.1 起因1.2 系统支持1.3 相关定义二 查看ACL权限2.1 简单查看2.2 详细查看2.3 具体配置三 更改ACL权限3.1 添加或修改ACL3.2 输出和输入3.3 设置ACL掩码3.4 递归修改ACL3.5 删除ACL3.6 控制默认ACL权限二 Linux磁盘分区中物理卷&…

zabbix6.0安装教程(七):从web界面安装

zabbix6.0安装教程&#xff08;七&#xff09;&#xff1a;从web界面安装 目录一、欢迎主界面二、先决条件检查三、配置数据库连通性四、配置本章节提供有关Zabbx Web界面的部署步骤说明。Zabbix 前端是由PHP语言编写&#xff0c;所以其网页服务的运行需要支持PHP语言的网站服务…

Allegro如何任意角度走线操作指导

Allegro如何任意角度走线操作指导 Allegro支持在PCB上进行任意角度走线,尤其是在高速设计的时候,尤为常见,如下图 具体操作如下 选择add connect命令Find选择Cline segs