IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> spring boot源码分析(三):配置文件的处理 -> 正文阅读

[Java知识库]spring boot源码分析(三):配置文件的处理

配置文件的处理

Spring Boot可以通过指定profile,将指定profile的配置加载到环境中,从而实现不同环境使用不同配置
背后的主要原理就是在应用启动的过程中,在不同阶段会向阶段监听器发送相应的阶段事件
当发送ApplicationEnvironmentPreparedEvent事件时,ConfigFileApplicationListener就会根据当前的profile,将满足条件的配置加载到环境中
下面详细看下源码

触发事件

当应用启动执行到prepareEnvironment时,会触发ApplicationEnvironmentPreparedEvent事件

// SpringApplication
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;
}
// SpringApplicationRunListener
void environmentPrepared(ConfigurableEnvironment environment) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.environmentPrepared(environment);
	}
}
// EventPublishingRunListener
public void environmentPrepared(ConfigurableEnvironment environment) {
	// 广播ApplicationEnvironmentPreparedEvent
	this.initialMulticaster
			.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

解析配置

首先看下ConfigFileApplicationListener的继承结构
在这里插入图片描述
可以看到实现了EnvironmentPostProcessor接口,因此可以对Environment进行修改
下面看下ConfigFileApplicationListener是如何响应ApplicationEnvironmentPreparedEvent事件的

// ConfigFileApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}
// ConfigFileApplicationListener
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	// <1> 通过spi的方式加载EnvironmentPostProcessor的子类实例
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	// <2> 将ConfigFileApplicationListener加入到postProcessors中
	postProcessors.add(this);
	// <3> 对多个postProcessor进行排序
	AnnotationAwareOrderComparator.sort(postProcessors);
	// <4> 遍历postProcessor,对environment进行处理
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
	}
}
// ConfigFileApplicationListener
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
	addPropertySources(environment, application.getResourceLoader());
}
// ConfigFileApplicationListener
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	// <1> 将RandomValuePropertySource添加到systemEnvironment之后
	RandomValuePropertySource.addToEnvironment(environment);
	// <2> 创创建Loader并调用load方法进行加载
	new Loader(environment, resourceLoader).load();
}

此时environment的propertySource中有如下几个配置来源
在这里插入图片描述
实际的解析操作交给内部类Loader进行处理

Loader

构造方法

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	// 需要处理的environment
	this.environment = environment;
	// 用来将配置中的占位符解析成实际的值
	this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
	// 统一资源加载
	this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
	// 通过spi的方式加载PropertySourceLoader的实现类实例,PropertySourceLoader用来加载PropertSource
	this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
			getClass().getClassLoader());
}

默认情况下,有如下两个ResourceLoader
在这里插入图片描述

load

void load() {
	// DEFAULT_PROPERTIES的值为defaultProperties
	// LOAD_FILTERED_PROPERTY 是一个set,包含spring.profiles.include和spring.profiles.active
	FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
			(defaultProperties) -> {
				this.profiles = new LinkedList<>();
				this.processedProfiles = new LinkedList<>();
				this.activatedProfiles = false;
				this.loaded = new LinkedHashMap<>();
				initializeProfiles();
				while (!this.profiles.isEmpty()) {
					Profile profile = this.profiles.poll();
					if (isDefaultProfile(profile)) {
						addProfileToEnvironment(profile.getName());
					}
					load(profile, this::getPositiveProfileFilter,
							addToLoaded(MutablePropertySources::addLast, false));
					this.processedProfiles.add(profile);
				}
				load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
				addLoadedPropertySources();
				applyActiveProfiles(defaultProperties);
			});
}
FilteredPropertySource.apply
// FilteredPropertySource
static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
			Consumer<PropertySource<?>> operation) {
	// <1> 从environment中获取当前需要处理的所有PropertySource
	MutablePropertySources propertySources = environment.getPropertySources();
	// <2> 不存在defaultProperties
	PropertySource<?> original = propertySources.get(propertySourceName);
	if (original == null) {
		operation.accept(null);
		return;
	}
	// <3> 存在defaultProperties,那么使用对应的PropertySource创建一个FilteredPropertySource
	propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
	try {
		operation.accept(original);
	}
	finally {
		// 再替换回来
		propertySources.replace(propertySourceName, original);
	}
}

上面的操作主要就是判断是否存在defaultProperties

  1. 不存在,operation.accept传入null
  2. 存在,operation.accept传入使用defaultProperties创建的FilteredPropertySource
accept
// ConfigFileApplicationListener
(defaultProperties) -> {
	this.profiles = new LinkedList<>();
	this.processedProfiles = new LinkedList<>();
	this.activatedProfiles = false;
	this.loaded = new LinkedHashMap<>();
	initializeProfiles();
	// 遍历profile
	while (!this.profiles.isEmpty()) {
		Profile profile = this.profiles.poll();
		// 如果不是默认profile,将当前profile作为生效profile,添加到environment中
		if (isDefaultProfile(profile)) {
			addProfileToEnvironment(profile.getName());
		}
		// <1> 加载配置
		load(profile, this::getPositiveProfileFilter,
				addToLoaded(MutablePropertySources::addLast, false));
		this.processedProfiles.add(profile);
	}
	load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
	// <2> 将加载到的配置添加到environment中
	addLoadedPropertySources();
	// <3> 重新设置activeProfile
	applyActiveProfiles(defaultProperties);
});
initializeProfiles

这个方法的左右就是初始化profile,通过读取environment中的spring.profiles.active和spring.profiles.include属性,将对应的profile添加到一个集合中
如果都没有指定,会创建一个名称为default的Profile添加到集合中

private void initializeProfiles() {
	// The default profile for these purposes is represented as null. We add it
	// first so that it is processed first and has lowest priority.
	// <1> 使用null代表默认的profile,具有最低的优先级
	this.profiles.add(null);
	// <2> 从所有的配置中获取spring.profiles.active对应的值
	Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
	// <3> 从所有的配置中获取spring.profiles.include对应的值
	Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
	// <4> 从environment中获取除了上面两个profile之外的active profile
	List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
	this.profiles.addAll(otherActiveProfiles);
	// Any pre-existing active profiles set via property sources (e.g.
	// System properties) take precedence over those added in config files.
	this.profiles.addAll(includedViaProperty);
	// 如果activatedViaProperty不为空,将profiles中defaultProfile为true的profile移除掉,并且将activatedViaProperty加入到profiles中
	addActiveProfiles(activatedViaProperty);
	// <5> 当没有显式指定profile时,会通过environment来获取默认profile的名称,然后添加到profile中
	if (this.profiles.size() == 1) { // only has null profile
		for (String defaultProfileName : this.environment.getDefaultProfiles()) {
			Profile defaultProfile = new Profile(defaultProfileName, true);
			this.profiles.add(defaultProfile);
		}
	}
}
load
// ConfigFileApplicationListener
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	// 通过读取配置来生成配置文件的路径,并进行加载
	getSearchLocations().forEach((location) -> {
		boolean isFolder = location.endsWith("/");
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
	});
}

下面的方法用来生成配置文件的搜索路径

// ConfigFileApplicationListener
private Set<String> getSearchLocations() {
	// 判断是否指定了spring.config.location属性,如果指定了,使用该属性的值作为配置文件搜索路径
	if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
		return getSearchLocations(CONFIG_LOCATION_PROPERTY);
	}
	// 没有指定spring.config.location
	// 将spring.config.additional-location属性的值作为搜索路径
	Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
	// 如果ConfigFileApplicationListener指定了searchLocations,那么添加到搜索路径中,否则将classpath:/,classpath:/config/,file:./,file:./config/添加到搜索路径
	locations.addAll(
			asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
	return locations;
}

一般情况下,我们不会指定spring.config.location和spring.config.additional-location,因此默认况下,配置文件的搜索目录就是下面四个

  1. classpath:/
  2. classpath:/config/
  3. file:./
  4. file:./config/

当搜索路径是目录时,会调用下面的方法来获取配置文件名,从而生成完整的配置文件路径

// ConfigFileApplicationListener
private Set<String> getSearchNames() {
	// 如果指定了spring.config.name配置,使用该配置的值作为配置文件的名称
	if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
		String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
		return asResolvedSet(property, null);
	}
	// 判断ConfigFileApplicationListener是否指定了names属性,如果指定了使用该属性,否则使用application
	return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}

一般情况下,我们不会指定spring.config.name,因此配置文件的名称就是application
下面看下加载具体配置文件的逻辑

// ConfigFileApplicationListener
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
	// <1> location是一个完整的文件路径
	// 遍历PropertySourceLoader,来判断是否可以处理该类型的文件
	if (!StringUtils.hasText(name)) {
		for (PropertySourceLoader loader : this.propertySourceLoaders) {
			if (canLoadFileExtension(loader, location)) {
				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 '/'");
	}
	// <2> location是一个目录
	// 遍历PropertySourceLoader,对配置文件进行加载
	Set<String> processed = new HashSet<>();
	for (PropertySourceLoader loader : this.propertySourceLoaders) {
		for (String fileExtension : loader.getFileExtensions()) {
			if (processed.add(fileExtension)) {
				loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
						consumer);
			}
		}
	}
}

接下来看下loadForFileExtension

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	// DocumentFiler用来对找到的配置文件进行过滤
	// <1> 创建DocumentFilter
	DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
	DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
	// <2> profile不为null,配置文件中带有profile的名称
	if (profile != null) {
		// Try profile-specific file & profile section in profile file (gh-340)
		// 比如application-default.properties,此时文件名中包含当前profile的名称
		String profileSpecificFile = prefix + "-" + profile + fileExtension;
		// 分别使用两个Document对找到的配置文件进行过滤,分别进行加载
		load(loader, profileSpecificFile, profile, defaultFilter, consumer);
		load(loader, profileSpecificFile, profile, profileFilter, consumer);
		// Try profile specific sections in files we've already processed
		// <3> 遍历之前已经处理过的profile的文件,将profile设置为当前profile,重新处理一遍
		for (Profile processedProfile : this.processedProfiles) {
			if (processedProfile != null) {
				String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
				load(loader, previouslyLoaded, profile, profileFilter, consumer);
			}
		}
	}
	// Also try the profile-specific section (if any) of the normal file
	// <4> 比如application.properties,此时文件名中不包含当前profile的名称
	load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

下面继续看下load

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
	try {
		// <1> 加载配置文件
		Resource resource = this.resourceLoader.getResource(location);
		// <2> 判断文件是否存在
		if (resource == null || !resource.exists()) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped missing config ", location, resource,
						profile);
				this.logger.trace(description);
			}
			return;
		}
		// <3> 判断文件是否有拓展名
		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 + "]";
		// <4> 将配置文件解析成Document
		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<>();
		// <5> 使用documentFilter对document进行过滤
		for (Document document : documents) {
			if (filter.match(document)) {
				// 将当前document中指定的activeProfiles加入到environment的activeProfile
				addActiveProfiles(document.getActiveProfiles());
				// 将当前document中指定的includeProfiles加入到需要解析的profiles队列中
				addIncludedProfiles(document.getIncludeProfiles());
				// 标记为已经加载
				loaded.add(document);
			}
		}
		Collections.reverse(loaded);
		// <6> 针对加载的document,调用consumer 
		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);
	}
}

下面看下是如何将Resource解析成Document的

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
				throws IOException {
	// 添加缓存逻辑,避免重复处理
	DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
	List<Document> documents = this.loadDocumentsCache.get(cacheKey);
	if (documents == null) {
		// <1> 使用PropertySourceLoader加载配置文件,生成PropertySource 
		// PropertiesPropertySourceLoader 用来加载properties为拓展名的配置文件
		// YamlPropertySourceLoader 用来加载yml为拓展名的配置文件
		List<PropertySource<?>> loaded = loader.load(name, resource);
		// <2> 将PropertySource转换成document
		documents = asDocuments(loaded);
		this.loadDocumentsCache.put(cacheKey, documents);
	}
	return documents;
}

这里不详细看如何加载配置文件,我们只要知道能将解析指定的文件解析成PropertySource即可,PropertySource中包含该文件指定的一些配置
这里详细看下asDocuments

private List<Document> asDocuments(List<PropertySource<?>> loaded) {
	if (loaded == null) {
		return Collections.emptyList();
	}
	// 这里在创建配置文件对应的Document对象时,同时会从解析后的结果中取出spring.profiles spring.profiles.active  spring.profiles.include的结果,作为构造参数,来构造Document
	return loaded.stream().map((propertySource) -> {
		Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),
				this.placeholdersResolver);
		return new Document(propertySource, binder.bind("spring.profiles", STRING_ARRAY).orElse(null),
				getProfiles(binder, ACTIVE_PROFILES_PROPERTY), getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
	}).collect(Collectors.toList());
}
DocumentFilter

DocumentFilter用于对配置文件生成的Documen进行过滤
有两个实现

  1. getPositiveProfileFilter
  2. getNegativeProfileFilter
    首先看下getPositiveProfileFilter
private DocumentFilter getPositiveProfileFilter(Profile profile) {
	return (Document document) -> {
		// <1> 如果入参profile为null,那么只会返回没有指定spring.profiles的配置文件
		if (profile == null) {
			return ObjectUtils.isEmpty(document.getProfiles());
		}
		// <2> 如果指定了profile,那么只会返回指定了spring.profiles,并且包含当前profile,并且当前profile包含在environment的activeProfiles中的配置文件
		return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
				&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
	};
}

接下来看下getNegativeProfileFilter

private DocumentFilter getNegativeProfileFilter(Profile profile) {
	// 首先,只有入参为null的时候才会返回文档
	// 其次,配置文件中指定了spring.profiles属性,并且spring.profiles中的值出现在activeProfiles中
	return (Document document) -> (profile == null && !ObjectUtils.isEmpty(document.getProfiles())
			&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles())));
}
DocumentConsumer

当生成配置文件对应的Document之后,会使用DocumentConsumer处理Document
具体实现如下

private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
				boolean checkForExisting) {
	return (profile, document) -> {
		// <1> 如果该配置文件已经加入到了loaded中,并且设置了checkForExisting为true,那么不会重复处理
		if (checkForExisting) {
			for (MutablePropertySources merged : this.loaded.values()) {
				if (merged.contains(document.getPropertySource().getName())) {
					return;
				}
			}
		}
		// 获取当前profile对应的MutablePropertySources
		MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
				(k) -> new MutablePropertySources());
		// 调用addMethod,将当前document对应的propertySource加到merged的最后或者最前
		addMethod.accept(merged, document.getPropertySource());
	};
}

其中,loader保存每个Profile和对应该Profile下的配置

private Map<Profile, MutablePropertySources> loaded;
addLoadedPropertySources
private void addLoadedPropertySources() {
	MutablePropertySources destination = this.environment.getPropertySources();
	List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
	Collections.reverse(loaded);
	String lastAdded = null;
	Set<String> added = new HashSet<>();
	for (MutablePropertySources sources : loaded) {
		for (PropertySource<?> source : sources) {
			if (added.add(source.getName())) {
				addLoadedPropertySource(destination, lastAdded, source);
				lastAdded = source.getName();
			}
		}
	}
}
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
				PropertySource<?> source) {
	// 如果包含defaultProperties,将当前配置添加到defaultProperties之前
	// 如果没有,就添加到最后,具有最低的优先级
	if (lastAdded == null) {
		if (destination.contains(DEFAULT_PROPERTIES)) {
			destination.addBefore(DEFAULT_PROPERTIES, source);
		}
		else {
			destination.addLast(source);
		}
	}
	else {
		destination.addAfter(lastAdded, source);
	}
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 16:13:08  更:2021-07-25 16:13:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 -2025/3/4 2:03:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码