springboot加载属性配置文件的步骤

springboot加载配置文件的方法在ConfigFileApplicationListener的load方法:

public void load() {
			this.profiles = new LinkedList<>();
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
			//初始化profiles对象,这行代码执行完之后,profiles有两个值:null和default
			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();
		}

以上代码需要注意的有如下几点:
1、initializeProfiles(); 该方法首先会往profiles属性中添加一个null,然后再判断属性属性里是否有“spring.profiles.active”和“spring.profiles.include”,如果没有,则添加一个“default”,如果有则添加对应的值。由于当前阶段springboot还未加载完成配置文件,因此那两个属性值可以设置在java启动参数中,如“java -jar --spring.profiles.active”
2、while (!this.profiles.isEmpty()) … 在while循环中第一代码Profile profile = this.profiles.poll(),该方法执行完之后profiles的数据会少一条。另一个更重要的是,在整个while循环中profiles的值是可能发生变化的,理解这一点非常关键,具体的变化我们在后面会讲到。
继续跟代码走到load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));这行,进入方法:

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

getSeachLocations()方法获取的HashSet包含如下值:
在这里插入图片描述
可以推断出,springboot模式就是从以上路径来查找配置文件的。
getSearchNames() 默认得到的是“aplication”,即表示配置文件名称叫做“aplication”,文件名后缀的处理后面会讲到。
进入到load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) 方法:

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

此方法中,this.propertySourceLoaders默认有两个值:PropertiesPropertySourceLoader和YamlPropertySourceLoader,再往下,loader.getFileExtensions()总共可能有三个值(propertise,xml,yml,yaml)。然后程序会尝试从location + name, “.” + fileExtension加载配置文件,由于我们的配置文件是放在classpath下的application.yml,因此我们直接进到location为“classpath:/”的时候介绍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);
			}
		}				

在这个方法中springboot首先从对应的路径将配置文件加载成document对象,注意看List documents = loadDocuments(loader, name, resource)这个方法里的documents = asDocuments(loaded)方法:

private List<Document> asDocuments(List<PropertySource<?>> loaded) {
			if (loaded == null) {
				return Collections.emptyList();
			}
			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());
		}

new Document的构造函数中第三个参数值为activeProfiles,由此可以看出系统会将配置文件中的spring.profiles.active取出来作为activeProfiles封装到document对象中去,因此我们再看addActiveProfiles(document.getActiveProfiles())这个方法时,就知道这个方法的参数document.getActiveProfiles()其实就是“dev”这个值

void addActiveProfiles(Set<Profile> profiles) {
			if (profiles.isEmpty()) {
				return;
			}
			if (this.activatedProfiles) {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied");
				}
				return;
			}
			this.profiles.addAll(profiles);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles));
			}
			this.activatedProfiles = true;
			removeUnprocessedDefaultProfiles();
		}

在这个方法里有两行重要的代码:this.profiles.addAll(profiles);表示将当前的dev这个profile加入到profiles对象中,第二行就是removeUnprocessedDefaultProfiles();表示删除未处理的、默认的default的profiles,至此我们可以回到最开始的while循环那里:

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

由此我们可以得到如下结论:一开始我们进入到while循环时,profiles的值有两个:null和default,在while循环中处理null这个profile的时候,系统会去加载classpath下的application.yml这个配置文件,然后由于配置文件中有spring.profiles.active=dev的配置,因此系统会将dev加入到profiles中,然后删除default这个profile,因此在while循环第二次处理时,Profile profile = this.profiles.poll();得到的值就是“dev”而不是default。再往后就是系统去加载dev这个profile,整个过程就不在复述了。

最后我们看一下加载完成之后,environment的propertySources的值如下:
在这里插入图片描述
而在刚初始化ConfigFileApplicationListener的load方法时,environment的值是这样的:
在这里插入图片描述

通过对比我们可以看到,在加载完成之后environment的propertySource中多了name为“server.port;applicationConfig:[classpath:/application-dev.yml];applicationConfig:[classpath:/application.yml];class path resource [application.yml]”。

以上场景只是我本地的一种最典型的配置文件加载方式的逻辑跟踪,实际上springboot支持多种指定配置文件的方式,实际场景肯定会更复杂。

最后我们看一下从environment获取属性的方式,跟踪environment.getProperty()方法到PropertySourcesPropertyResolver的getProperty方法:

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
			for (PropertySource<?> propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
							propertySource.getName() + "'");
				}
				Object value = propertySource.getProperty(key);
				if (value != null) {
					if (resolveNestedPlaceholders && value instanceof String) {
						value = resolveNestedPlaceholders((String) value);
					}
					logKeyFound(key, propertySource, value);
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Could not find key '" + key + "' in any property source");
		}
		return null;
	}

从以上代码可以看到,从environment获取属性时,实际上是从上往下查找environment中的各个propertySources,一旦找到就返回。

至此,整个springboot加载配置文件的过程就简单分析到这里了,大家遇到实际的问题还是要自己打断点去分析,我这只是给大家提供一个分析和参考,让大家知道关键的点在哪里,断点可以打在哪里。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值