上篇文章介绍了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这一部分)

Springboot设置资源目录 springboot配置文件加载源码_spring boot


紧接着第四行代码,如果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

Springboot设置资源目录 springboot配置文件加载源码_加载_02


启动项目,在ApplicationEnvironmentPreparedEvent事件处理结束的地方打断点,观察下Environment内部PropertySource列表的内容,加载的配置文件以及顺序符合我们前文的推测

Springboot设置资源目录 springboot配置文件加载源码_spring boot_03