Spring Boot常用功能Profile详解

 更新时间:2022年07月15日 09:27:57   作者:lhf2112  
SpringBootProfile是一个很常用的功能,我们可以通过为开发/测试/生产环境配置不同的profile来实现配置隔离,那么在SpringBoot项目中是如何实现profile功能的呢

入口

相关逻辑的入口是listener类:ConfigFileApplicationListener,当容器广播器触发ApplicationEnvironmentPreparedEvent事件时,ConfigFileApplicationListener会收到广播器的通知,进而执行onApplicationEnvironmentPreparedEvent方法

入口处代码:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

接下来onApplicationEnvironmentPreparedEvent方法会加载容器中的EnvironmentPostProcessor并进行遍历,调用他们的postProcessEnvironment方法,我们可以看一下此时的PostProcessor有哪些:

我们发现ConfigFileApplicationListener本身也是其中一个PostProcessor :)

我们直接进入ConfigFileApplicationListener的postProcessEnvironment方法,它会调用一个addPropertySources方法

	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();
	}

随机属性值部分暂且不表,我们看下loader.load方法

		public void load() {
			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 (profile != null && !profile.isDefaultProfile()) {
					addProfileToEnvironment(profile.getName());
				}
				load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
			resetEnvironmentProfiles(this.processedProfiles);
			load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
			addLoadedPropertySources();
		}

load方法的重点逻辑-initializeProfiles()

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.
	this.profiles.add(null);
	Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
	this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
	// Any pre-existing active profiles set via property sources (e.g.
	// System properties) take precedence over those added in config files.
	addActiveProfiles(activatedViaProperty);
	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);
		}
	}
}

add(null) 可以处理application.properties/yml,接下来getProfilesActivatedViaProperty方法会从spring.profiles.active和spring.profiles.include配置中读取激活的profile~(这两者也是相对常用的配置);再接下来会判断profiles大小是否为1,是的话会添加一个default profile,如图所示:

这也解释了为什么spring.profiles.default必须定义在其他属性源(命令行启动参数),因为这时候分散文件属性元还没有被解析到!

回到load方法,如果profiles不为空,就会进行while遍历,对其调用另外一个load方法:

(load-number2方法):

		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));
			});
		}

我们可以看到还会调用到另一个load方法

(load-number3方法):

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)) {
				load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
				return;
			}
		}
	}
	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);
			}
		}
	}
}

这里的propertySourceLoaders包括:

是不是很熟悉?这就是properties和yaml分别对应的PropertySourceLoader~

下面代码会调用loadForFileExtension方法,而这个方法又会调用新的load方法(😓):

(load-number4方法):

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);
	}
}

这个方法会读取application-profile.properties/yaml文件,并加载文件内激活的profile(如果有的话)

到这里已经读取到了所有的配置信息,主逻辑大概结束了,还差最后一个步骤: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();
					}
				}
			}
		}

这里会将所有属性加入到“destination”,即environment.getPropertySources(),到这里,加载配置属性的逻辑就完成了。

到此这篇关于Spring Boot常用功能Profile详解的文章就介绍到这了,更多相关Spring Boot Profile内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 实例分析java开启线程的方法

    实例分析java开启线程的方法

    在本文里我们通过实例给大家讲解了JAVA开启线程的方法和相关知识点,需要的朋友们跟着学习下。
    2019-03-03
  • java中ResultSet遍历数据操作

    java中ResultSet遍历数据操作

    这篇文章主要介绍了java中ResultSet遍历数据操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 详解5种Java中常见限流算法

    详解5种Java中常见限流算法

    在高并发系统中,出于系统保护角度考虑,通常会对流量进行限流;不但在工作中要频繁使用,而且也是面试中的高频考点。本文就为大家整理了5种Java中常见限流算法,需要的可以参考一下
    2023-04-04
  • Spring多线程事务处理解决方案

    Spring多线程事务处理解决方案

    这篇文章主要介绍了Spring多线程事务处理,本文主要介绍了spring多线程事务的解决方案,心急的小伙伴可以跳过上面的理论介绍分析部分直接看最终解决方案,需要的朋友可以参考下
    2024-03-03
  • maven工程中读取resources中的资源文件

    maven工程中读取resources中的资源文件

    Web项目中应该经常有这样的需求,在maven项目的resources目录下放一些文件,比如一些配置文件,资源文件等,本文主要介绍了maven工程中读取resources中的资源文件,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • Java解析变量公式的简单示例

    Java解析变量公式的简单示例

    在Java编程中,经常会遇到需要解析表达式或公式的情况,特别是涉及到动态计算或配置项的场景,在本篇文章中,我将介绍如何在Java中解析变量公式,并给出一个简单的实现示例,需要的朋友可以参考下
    2024-10-10
  • idea2020.3测试评价及感受

    idea2020.3测试评价及感受

    idea2020.3版本这次变化最大的也就是 UI了完全抛弃了之前一直使用的模板更改成了新的样式,感兴趣的朋友快来下载体验下吧
    2020-10-10
  • 浅谈Thread.sleep(0)到底有什么用

    浅谈Thread.sleep(0)到底有什么用

    为什么要用sleep,主要是为了暂停当前线程,把cpu片段让出给其他线程,减缓当前线程的执行,本文主要介绍了Thread.sleep(0)到底有什么用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • 解决IDEA创建maven项目时pom.xml没有变蓝的问题

    解决IDEA创建maven项目时pom.xml没有变蓝的问题

    这篇文章主要介绍了解决IDEA创建maven项目时pom.xml没有变蓝的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • Java日期时间操作的方法

    Java日期时间操作的方法

    这篇文章主要为大家详细介绍了Java日期时间操作的一些方法,获得Calendar,定义日期/时间的格式等,感兴趣的小伙伴们可以参考一下
    2016-08-08

最新评论