上篇文章介绍了EnvironmentPostProcessor接口,还遗留了一行代码,也就是下面addPropertySources方法的第二行,这行代码完成了配置文件的加载,本文将详细分析这一过程
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
(new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
}
首先new了一个内部类Loader,构造函数传入了Environment和ResourceLoader,这个ResourceLoader是从SrpingApplication中获取的,就像上文所讲,其实是个null值
看下Loader的结构以及构造函数
private class Loader {
private final Log logger;
private final ConfigurableEnvironment environment;
private final PropertySourcesPlaceholdersResolver placeholdersResolver;
private final ResourceLoader resourceLoader;
private final List<PropertySourceLoader> propertySourceLoaders;
private Deque<ConfigFileApplicationListener.Profile> profiles;
private List<ConfigFileApplicationListener.Profile> processedProfiles;
private boolean activatedProfiles;
private Map<ConfigFileApplicationListener.Profile, MutablePropertySources> loaded;
private Map<ConfigFileApplicationListener.DocumentsCacheKey, List<ConfigFileApplicationListener.Document>> loadDocumentsCache;
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.logger = ConfigFileApplicationListener.this.logger;
this.loadDocumentsCache = new HashMap();
this.environment = environment;
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
this.resourceLoader = (ResourceLoader)(resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, this.getClass().getClassLoader());
}
变量比较多,先简单说下每个成员变量的作用,后续随着分析的进程再详细解释,有些看不懂的可以暂时不管,后面都会说到
- environment:这个没什么好说的,已经打过很多交道了
- placeholdersResolver:在构造函数中初始化为PropertySourcesPlaceholdersResolver
public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver {
......
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
this.sources = sources;
this.helper = helper != null ? helper : new PropertyPlaceholderHelper("${", "}", ":", true);
}
}
通过类名以及构造函数里的前缀" $ { “,后缀” } “,分隔符”:" ,不难想到它主要是用来处理配置文件中的占位符,比如server.port=${port}
- resourceLoader:在构造函数中初始化为DefaultResourceLoader,我们上篇文章最后的demo使用过这个实现类,它可以把一个指定位置的文件抽象成Resource,供后续解析使用
- propertySourceLoaders:构造函数的最后一行,从META-INF/spring.factories中加载了PropertySourceLoader的实现类,到spring-boot的spring.factories可以找到有两个实现:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
看名字就应该知道是干嘛的了,分别是加载properties和yml格式配置文件的解析器
- profiles:存储需要解析的profile
- processedProfiles:存储解析过的profile
这两个变量都是Profile类型,它是在ConfigFileApplicationListener中声明的内部类,只是存储了profile的名字以及是否默认配置的标记,只有在没有通过任何方式指定profile的时候,才会加载default配置,其他profile该属性都是false
private static class Profile {
private final String name;
private final boolean defaultProfile;
- activatedProfiles:布尔值,该属性用来限制只从一个渠道加载activeProfile,如果最初通过系统参数,如-Dspring.profiles.active=fat指定了profile为fat,那么后续配置文件中的就失效了,比如application.properties中配置了属性spirng.profiles.active=sit不再生效
- loaded:key为Profile,value为MutablePropertySources的map,Profile我们刚刚介绍过,MutablePropertySources我们也很熟悉了,它里面可以存储一个PropertySouce的列表,这里的key对应各种profile,包括最初添加的null,我们后续会见到,value用来存储根据某个profile找到的所有配置文件(有可能在不同目录,或者不同的文件类型,不同的后缀)
比如我们指定了profile为fat,然后在classpath下同时存在application-fat.properties和application-fat.yml,甚至还有个config目录下也包含application-fat.yml,那么最终这个fat对应的MutablePropertySources中就会有三个PropertySource - loadDocumentsCache:key为DocumentCacheKey,value为 List< Document >>的map,这两个类也是ConfigFileApplicationListener的内部类,顾名思义它是一个缓存,当某个解析器(property解析器或者yml解析器)将某个路径下的配置文件解析成Document后,缓存起来,下次再调用直接从缓存中返回结果(实际会发生很多次重复的解析,除第一次外都从缓存中取)
Document是对配置文件的抽象,后续读取配置文件,比如application.properties,会先把它转化为Document,其中的propertySource就存储了文件中的所有属性,其它几个参数用来存储一些特殊配置,比如application.properties中有spring.profiles.active=fat的配置,就会解析到activeProfiles属性中,如果没有通过系统参数指定,那么fat会加入到Loader.profiles中进行解析
private static class Document {
private final PropertySource<?> propertySource;
private String[] profiles;
private final Set<ConfigFileApplicationListener.Profile> activeProfiles;
private final Set<ConfigFileApplicationListener.Profile> includeProfiles;
DocumentsCacheKey就是单纯的缓存键,loader代表了解析器类型是properties还是yml,Resource就是特定的一个资源,比如classpath下的application-fat.properties
private static class DocumentsCacheKey {
private final PropertySourceLoader loader;
private final Resource resource;
创建Loader完成后,紧接着就调用了它的load方法
前几行是对Loader的成员变量做初始化,这几行过后结合构造方法,所有Loader的成员们变量就都已经赋了初值
public void load() {
this.profiles = new LinkedList();
this.processedProfiles = new LinkedList();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap();
this.initializeProfiles();
while(!this.profiles.isEmpty()) {
ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
this.addProfileToEnvironment(profile.getName());
}
this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
this.resetEnvironmentProfiles(this.processedProfiles);
this.load((ConfigFileApplicationListener.Profile)null, this::getNegativeProfileFilter, this.addToLoaded(MutablePropertySources::addFirst, true));
this.addLoadedPropertySources();
}
我们先看initializeProfiles方法
private void initializeProfiles() {
this.profiles.add((Object)null);
Set<ConfigFileApplicationListener.Profile> activatedViaProperty = this.getProfilesActivatedViaProperty();
this.profiles.addAll(this.getOtherActiveProfiles(activatedViaProperty));
this.addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) {
String[] var2 = this.environment.getDefaultProfiles();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
String defaultProfileName = var2[var4];
ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
先往profiles里添加了一个空元素,这个元素相当于是一个占位符的作用,后续遍历的时候,首先加载没有profile后缀的的默认配置文件,比如先加载application.properties,再加载application-fat.properties
然后第二行代码调用getProfilesActivatedViaProperty,到environment中找spring.profiles.active和spring.profiles.include属性,将值封装成内部类Profile,defaultProfile值为false
private Set<ConfigFileApplicationListener.Profile> getProfilesActivatedViaProperty() {
if (!this.environment.containsProperty("spring.profiles.active") && !this.environment.containsProperty("spring.profiles.include")) {
return Collections.emptySet();
} else {
Binder binder = Binder.get(this.environment);
Set<ConfigFileApplicationListener.Profile> activeProfiles = new LinkedHashSet();
activeProfiles.addAll(this.getProfiles(binder, "spring.profiles.include"));
activeProfiles.addAll(this.getProfiles(binder, "spring.profiles.active"));
return activeProfiles;
}
}
第三行调用getOtherActiveProfiles,传入上面找到的viaProfiles,把结果添加到loader的profiles中
private List<ConfigFileApplicationListener.Profile> getOtherActiveProfiles(Set<ConfigFileApplicationListener.Profile> activatedViaProperty) {
return (List)Arrays.stream(this.environment.getActiveProfiles()).map(ConfigFileApplicationListener.Profile::new).filter((profile) -> {
return !activatedViaProperty.contains(profile);
}).collect(Collectors.toList());
}
还记得上上篇我们讲activeProfiles初始化的时候,看到environment中有一个activeProfiles属性,它也找了spring.profiles.active,除此之外,它还合并了SpringApplication的additionalProfiles属性,这里其实就是把activeProfiles中存在的,而viaProfiles中没有的,先添加到loader.profiles中(也就是additionalProfiles这一部分)
紧接着第四行代码,如果viaProfiles不为空的话,把viaProfiles添加到loader.profiles中,同时将activatedProfiles属性修改为true,这里修改为true后,后续配置文件中的spring.profiles.active属性就失效了,它们会再次进入这个方法,发现this.activatedProfiles==true,就只会打印一行日志;当然,如果这里viaProfiles为空,那么后续进来就会把配置文件中指定的profile添加到loader.profiles中,同时删除default类型的profile
void addActiveProfiles(Set<ConfigFileApplicationListener.Profile> profiles) {
if (!profiles.isEmpty()) {
if (this.activatedProfiles) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied");
}
} else {
this.profiles.addAll(profiles);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles));
}
this.activatedProfiles = true;
this.removeUnprocessedDefaultProfiles();
}
}
}
回到initializeProfiles方法的流程,接下来就判断了loader.profiles.size是否等于1,如果是,即profiles中只有第一行添加的null,说明没有做任何关于profile的配置,就先添加default配置,调用的是environment的getDefaultProfiles方法
public String[] getDefaultProfiles() {
return StringUtils.toStringArray(this.doGetDefaultProfiles());
}
protected Set<String> doGetDefaultProfiles() {
Set var1 = this.defaultProfiles;
synchronized(this.defaultProfiles) {
if (this.defaultProfiles.equals(this.getReservedDefaultProfiles())) {
String profiles = this.getProperty("spring.profiles.default");
if (StringUtils.hasText(profiles)) {
this.setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles)));
}
}
return this.defaultProfiles;
}
}
它先找了spring.profiles.default属性,即使我们没有配置,也会有一个默认的值default,在defaultProfiles声明的时候就已经做了初始化
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
......
......
private final Set<String> defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles());
protected Set<String> getReservedDefaultProfiles() {
return Collections.singleton("default");
}
......
如果将来加载application.properties(或者application.yml等默认配置)时,发现其中指定了profile,那么就会把这里添加的default类型的配置删除,转而去加载文件中指定的配置
回到Loader.load()方法,initializeProfiles执行完毕,它的profiles属性就有两种情况,要么是null + default,要么就是null + 我们自己配置的profile,如null+fat等
接下来就对profiles做遍历,前面的if就是判断是否default,不是的话就添加到environment的activeProfiles属性中,本身有些profile就是来自activeProfiles,因为environment的这个属性是一个set集合,所以重复添加也没关系
然后调用load方法进行加载,加载完成后把当前profile添加到已经加载过的集合processedProfiles中
......
while(!this.profiles.isEmpty()) {
ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
this.addProfileToEnvironment(profile.getName());
}
this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
......
重点是后面的load方法,先看下方法的声明
private void load(ConfigFileApplicationListener.Profile profile,
ConfigFileApplicationListener.DocumentFilterFactory filterFactory,
ConfigFileApplicationListener.DocumentConsumer consumer) {
其中DocumentFilterFactory 和DocumentConsumer 都是CongFileApplicationListener声明的函数式接口,它一共声明了三个函数式接口
@FunctionalInterface
private interface DocumentConsumer {
void accept(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.Document document);
}
@FunctionalInterface
private interface DocumentFilter {
boolean match(ConfigFileApplicationListener.Document document);
}
@FunctionalInterface
private interface DocumentFilterFactory {
ConfigFileApplicationListener.DocumentFilter getDocumentFilter(ConfigFileApplicationListener.Profile profile);
}
通过名字可以知道都和Document有关
- DocumentFilter用来判断是否要将一个Document添加到系统配置中
- DocumentFilterFactory就是生产一个DocumentFilter
- DocumentConsumer用来对Document做一些处理
我们再看下load方法传进去的参数,第二个参数是一个方法引用
它根据入参的Profile是否为null来返回不同的DocumentFilter,具体每个Filter的作用我们后续用到的时候再分析
private ConfigFileApplicationListener.DocumentFilter getPositiveProfileFilter(ConfigFileApplicationListener.Profile profile) {
return (document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
} else {
return ObjectUtils.containsElement(document.getProfiles(), profile.getName()) && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
}
};
}
第三个参数调用了addToLoaded方法,返回一个DocumentConsumer ,它的参数为一个函数引用和一个bool值,这个Consumer的逻辑我们也是后面用到了再说
private ConfigFileApplicationListener.DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod, boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) {
Iterator var5 = this.loaded.values().iterator();
while(var5.hasNext()) {
MutablePropertySources merged = (MutablePropertySources)var5.next();
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
MutablePropertySources mergedx = (MutablePropertySources)this.loaded.computeIfAbsent(profile, (k) -> {
return new MutablePropertySources();
});
addMethod.accept(mergedx, document.getPropertySource());
};
}
这里多提一句,addToLoaded方法第一个参数是一个BiConsumer,它的方法接收两个参数
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
......
}
而我们传进去的MutablePropertySources::addLast是只有一个入参的方法
public void addLast(PropertySource<?> propertySource) {
this.removeIfPresent(propertySource);
this.propertySourceList.add(propertySource);
}
这是因为方法引用本身包含了一个隐含的参数
MutablePropertySources::addLast
可以等价为
(MutablePropertySources m, PropertySource p) -> m.addLast(p)
言归正传,进入load方法的实现
private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
this.getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
this.load(location, name, profile, filterFactory, consumer);
});
});
}
先通过getSearchLocations获取要查找配置文件的目录
private Set<String> getSearchLocations() {
if (this.environment.containsProperty("spring.config.location")) {
return this.getSearchLocations("spring.config.location");
} else {
Set<String> locations = this.getSearchLocations("spring.config.additional-location");
locations.addAll(this.asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/"));
return locations;
}
}
可以看到如果没有配置spring.config.location,那么默认路径是classpath、classpath下的config目录、当前目录、当前目录/config
实际遍历的顺序依次是当前目录/config、当前目录、classpath/config、classpath,而获取DocumentConsumer的时候传的第一个参数是addLast,代表了会依次将找到的配置文件添加到List的末尾,也就是说先遍历到的,就拥有更高的优先级
假如你的classpath/config和classpath下同时包含application.properties,且都有server.port属性,最后生效的将是classpath/config下配置的端口
然后再看下遍历的逻辑,先判断当前路径是否为目录,默认的四个location都是以 / 结尾的,进入getSearchNames方法
private Set<String> getSearchNames() {
if (this.environment.containsProperty("spring.config.name")) {
String property = this.environment.getProperty("spring.config.name");
return this.asResolvedSet(property, (String)null);
} else {
return this.asResolvedSet(ConfigFileApplicationListener.this.names, "application");
}
}
如果没有通过spring.config.name指定配置文件的名字,就默认使用application,这就是SpringBoot配置文件默认为application.properties或application.yml的原因
取到配置文件名字后,再对名字做个遍历,到本次循环的目录下,根据指定名字找配置文件,调用了重载的load方法
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
Iterator var6 = this.propertySourceLoaders.iterator();
while(var6.hasNext()) {
PropertySourceLoader loader = (PropertySourceLoader)var6.next();
if (this.canLoadFileExtension(loader, location)) {
this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
Set<String> processed = new HashSet();
Iterator var14 = this.propertySourceLoaders.iterator();
while(var14.hasNext()) {
PropertySourceLoader loaderx = (PropertySourceLoader)var14.next();
String[] var9 = loaderx.getFileExtensions();
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String fileExtension = var9[var11];
if (processed.add(fileExtension)) {
this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}
}
}
}
第一个if分支是通过手动配置了目录地址,并且不以 / 结尾才会走,我们先跳过,接下来获取了propertySourceLoaders集合,其中包含一个properties解析器和一个yml解析器,并在这个集合上循环,获取其后缀,对后缀再进行循环调用loadForFileExtension方法
properties解析器的后缀有两种,xml和properties,yml解析器的后缀也是两种,yml和yaml,这也就是为什么我们yml格式的配置文件后缀可以是yml,也可以是yaml
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
......
public String[] getFileExtensions() {
return new String[]{"properties", "xml"};
}
......
}
public class YamlPropertySourceLoader implements PropertySourceLoader {
......
public String[] getFileExtensions() {
return new String[]{"yml", "yaml"};
}
......
}
到这里已经嵌套了很多层的循环遍历了,我们先小结一下
默认情况下一共有4个目录,classpath、classpath/config、当前目录、当前目录/config,然后有两种解析器,每个解析器有两种后缀
那么总结下来,在最外层Loader#load方法的while循环中,每个profile最少要调用4 * 2 * 2 = 16次loadForFileExtension方法
目录优先级:当前目录/config > 当前目录 > classpath/config > classpath
配置文件类型优先级:properties > yml
后缀类型优先级: .properties > .xml , .yml > .yaml
再跟进loadForFileExtension方法
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
ConfigFileApplicationListener.DocumentFilter defaultFilter = filterFactory.getDocumentFilter((ConfigFileApplicationListener.Profile)null);
ConfigFileApplicationListener.DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
String profileSpecificFile = prefix + "-" + profile + fileExtension;
this.load(loader, profileSpecificFile, profile, defaultFilter, consumer);
this.load(loader, profileSpecificFile, profile, profileFilter, consumer);
Iterator var10 = this.processedProfiles.iterator();
while(var10.hasNext()) {
ConfigFileApplicationListener.Profile processedProfile = (ConfigFileApplicationListener.Profile)var10.next();
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
this.load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
先通过当前profile和null分别从DocumentFilterFactory中获取两个DocumentFilter
由于loader.proflles最先添加了一个null,所以它走到这里的时候,这两个Filter是完全一样的,并且它不会进下面的if,直接进入最后一行,加载名为application的配置文件
如果是其它profile不为null的时候,比如profile=fat,进入这段代码,就会进入if分支,将文件名和profile用-连接,也就是加载名为application-fat的配置文件,并且会用两个DocumentFilter分别加载一次
我们回头看下两个DocumentFilter的逻辑
private ConfigFileApplicationListener.DocumentFilter getPositiveProfileFilter(ConfigFileApplicationListener.Profile profile) {
return (document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
} else {
return ObjectUtils.containsElement(document.getProfiles(), profile.getName()) && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
}
};
}
不难发现两个Filter的检测条件其实是相冲的,只会有一个返回true
defaultFilter检测document中的profiles属性是否为空
profileFilter检测document中的profiles是否包含当前profile,并且都在Environment中激活了
这里提前说一下,配置文件解析成Document后,其profiles属性基本都是空的,我还没见过有哪个项目在配置文件中添加spring.profiles属性来指定配置,倒是spring.profiles.active出现的比较多,通过spring.profiles指定了特定的profile还会屏蔽掉默认application的内容,这个分支的代码各位可以自己去看看,流程大体一样,就不再详细解释了
所以这里基本都是通过defaultFilter完成加载,后面根据profileFilter加载的时候,会被这个profileFilter过滤掉
然后加载完后,又遍历了已经加载过的集合processedProfiles,用profileFilter又加载了一遍,也会被过滤
最后if分支结束,调用最后一行代码,尝试用profileFilter再次加载名为application的配置文件,不过依然会被过滤掉
通过上面的流程分析,我们可以知道,会有大量的重复加载,虽然都被profileFilter过滤了,但是在调用Filter的match方法之前,还是要先把配置文件抽象成Document的,这就是我们最初介绍的,Loader里的成员变量loadDocumentsCache的作用,它会缓存加载的结果,有重复的加载就直接从缓存里取
说了这么多,我们该进入这最后的load方法中看一看了
private void load(PropertySourceLoader loader, String location, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilter filter, ConfigFileApplicationListener.DocumentConsumer consumer) {
try {
Resource resource = this.resourceLoader.getResource(location);
StringBuilder descriptionxx;
if (resource != null && resource.exists()) {
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
descriptionxx = this.getDescription("Skipped empty config extension ", location, resource, profile);
this.logger.trace(descriptionxx);
}
} else {
String name = "applicationConfig: [" + location + "]";
List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = this.getDescription("Skipped unloaded config ", location, resource, profile);
this.logger.trace(description);
}
} else {
List<ConfigFileApplicationListener.Document> loaded = new ArrayList();
Iterator var10 = documents.iterator();
while(var10.hasNext()) {
ConfigFileApplicationListener.Document document = (ConfigFileApplicationListener.Document)var10.next();
if (filter.match(document)) {
this.addActiveProfiles(document.getActiveProfiles());
this.addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((documentx) -> {
consumer.accept(profile, documentx);
});
if (this.logger.isDebugEnabled()) {
StringBuilder descriptionx = this.getDescription("Loaded config file ", location, resource, profile);
this.logger.debug(descriptionx);
}
}
}
}
} else {
if (this.logger.isTraceEnabled()) {
descriptionxx = this.getDescription("Skipped missing config ", location, resource, profile);
this.logger.trace(descriptionxx);
}
}
} catch (Exception var12) {
throw new IllegalStateException("Failed to load property source from location '" + location + "'", var12);
}
}
大致流程,就是根据传进来的路径,通过DefaultResourceLoader把配置文件抽象成Resouce,然后再转化为Document,紧接着用传进来的DocumentFilter对它做一下match,通过的话对它应用DocumentConsumer,我们来具体看下Consumer的逻辑
private ConfigFileApplicationListener.DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod, boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) {
Iterator var5 = this.loaded.values().iterator();
while(var5.hasNext()) {
MutablePropertySources merged = (MutablePropertySources)var5.next();
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
MutablePropertySources mergedx = (MutablePropertySources)this.loaded.computeIfAbsent(profile, (k) -> {
return new MutablePropertySources();
});
addMethod.accept(mergedx, document.getPropertySource());
};
}
在最外层的while循环中,传进来的checkForExisting都是false,也就是说不会走这段逻辑,它是为最后兜底加载默认配置application准备的,作用也是防止重复加载
接下来把当前配置添加到profile对应的MutablePropertySources中,如果没有就新建一个
至此,最外层load方法的while循环就分析完了,此时已经完成了所有profile文件的解析,并按照profile分组,存在了单独的MutablePropertySources中,我们继续看最后三行代码
public void load() {
this.profiles = new LinkedList();
this.processedProfiles = new LinkedList();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap();
this.initializeProfiles();
while(!this.profiles.isEmpty()) {
ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
this.addProfileToEnvironment(profile.getName());
}
this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
this.resetEnvironmentProfiles(this.processedProfiles);
this.load((ConfigFileApplicationListener.Profile)null, this::getNegativeProfileFilter, this.addToLoaded(MutablePropertySources::addFirst, true));
this.addLoadedPropertySources();
}
resetEnvironmentProfiles方法用已经解析过的配置文件,即processedProfiles更新environment的activeProfiles,后续需要知道系统真正应用了哪些profile,直接从environment的activeProfiles中取
private void resetEnvironmentProfiles(List<ConfigFileApplicationListener.Profile> processedProfiles) {
String[] names = (String[])processedProfiles.stream().filter((profile) -> {
return profile != null && !profile.isDefaultProfile();
}).map(ConfigFileApplicationListener.Profile::getName).toArray((x$0) -> {
return new String[x$0];
});
this.environment.setActiveProfiles(names);
}
然后又调用了一次load,第一个Profile参数传了null,第二个参数DocumentFiterFactory跟之前传的不一样,它的match条件包含了document.getProfiles不为空,所以按上面分析的流程,它也会被过滤掉
private ConfigFileApplicationListener.DocumentFilter getNegativeProfileFilter(ConfigFileApplicationListener.Profile profile) {
return (document) -> {
return profile == null && !ObjectUtils.isEmpty(document.getProfiles()) && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
};
}
第三个参数的区别是添加PropertySource用的是addFirst方法,并且checkForExisting设置为true,即会做验重处理
实际在上面的while循环里,第一个处理的profile就是null,所以它已经加载过了,无论是DocumentFilter的match还是DocumentConsumer的验重,都不会通过的,这里的作用我认为是一个兜底,确保默认配置会被加载
最后一行代码addLoadedPropertySources,就把刚才找到的这些配置文件,添加到Environment的PropertySource列表中
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();
Iterator var5 = loaded.iterator();
while(var5.hasNext()) {
MutablePropertySources sources = (MutablePropertySources)var5.next();
Iterator var7 = sources.iterator();
while(var7.hasNext()) {
PropertySource<?> source = (PropertySource)var7.next();
if (added.add(source.getName())) {
this.addLoadedPropertySource(destination, lastAdded, source);
lastAdded = source.getName();
}
}
}
}
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded, PropertySource<?> source) {
if (lastAdded == null) {
if (destination.contains("defaultProperties")) {
destination.addBefore("defaultProperties", source);
} else {
destination.addLast(source);
}
} else {
destination.addAfter(lastAdded, source);
}
}
最后做下验证,假设我们设置了profile为fat
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(new Class[]{Application.class});
springApplication.setAdditionalProfiles("fat");
springApplication.run(args);
}
}
然后在classpath下创建config目录,里面新建application-fat.properties和application-fat.yml,并在classpath下新建application-fat.yml和application-sit.yml
启动项目,在ApplicationEnvironmentPreparedEvent事件处理结束的地方打断点,观察下Environment内部PropertySource列表的内容,加载的配置文件以及顺序符合我们前文的推测