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