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

一.知识回顾

【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】就先学习到这里,更多的内容持续学习创作中,敬请期待。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

硕风和炜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值