SpringBoot属性文件加载原理

68 阅读5分钟

SpringBoot配置文件的加载是依靠监听机制来实现的,是通过ConfigFileApplicationListener这个监听器来实现的。

加载属性文件的一个流程图

源码分析

在SpringApplication(primarySources)这个构造方法里面加载Springboot内置的监听器,包含我们的配置文件处理器监听器ConfigFileApplicationListener,如果是Cloud项目还会加载BootstrapApplicationListener。

在run通过getRunListeners(args)方法加载事件发布器EventPublishingRunListener,然后发布starting事件,在prepareEnvironment(listeners, applicationArguments)环境准备阶段,去发布environmentPrepared事件,在这个事件发布的时候会触发ConfigFileApplicationListener这个监听器。在ConfigFileApplicationListener这个监听器里面就负责加载 .properties、.xml、.yml、.yaml 配置文件

环境准备prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // 创建并配置Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置PropertySources和activeProfiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 在配置环境信息之前发布事件
    listeners.environmentPrepared(environment);
    // 把相关的配置信息绑定到Spring容器中
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                                                                                               deduceEnvironmentClass());
    }
    // 配置PropertySources对它自己的递归依赖
    ConfigurationPropertySources.attach(environment);
    return environment;
}

发布事件environmentPrepared以后触发ConfigFileApplicationListener里面的onApplicationEvent(ApplicationEvent event)方法

public void onApplicationEvent(ApplicationEvent event) {
    //  如果是ApplicationEnvironmentPreparedEvent 则处理
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    // 如果是 ApplicationPreparedEvent 则处理
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

配置文件内容加载onApplicationEnvironmentPreparedEvent

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    // 加载系统提供的环境配置的后置处理器
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    // 添加自身即 ConfigFileApplicationListener 为后置处理器
    postProcessors.add(this);
    // 原来有4个,现在加了一个需要重新排序
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        // 系统提供的4个不是重点,重点是ConfigFileApplicationListener 中的这个方法处理
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}

后置处理方法

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    // 属性文件的加载解析在这个方法里面
    addPropertySources(environment, application.getResourceLoader());
}

addPropertySources 将配置文件属性源添加到指定环境

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

Loader 加载候选属性源并配置活动配置文件

Loader构造方法

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    // environment 对象的赋值
    this.environment = environment;
    // 占位符处理
    this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
    // 资源加载器
    this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
    // 重点:获取spring.factories 中配置的属性加载器,用来加载解析properties文件或者是yml文件
    this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
            getClass().getClassLoader());
}

PropertiesPropertySourceLoader 类

负责处理.properties、.xml配置文件

public class PropertiesPropertySourceLoader implements PropertySourceLoader {
    private static final String XML_FILE_EXTENSION = ".xml";
    @Override
    public String[] getFileExtensions() {
        return new String[] { "properties", "xml" };
    }
    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        // 获取属性文件中的信息
        Map<String, ?> properties = loadProperties(resource);
        if (properties.isEmpty()) {
            return Collections.emptyList();
        }
        return Collections
        .singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
    }
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Map<String, ?> loadProperties(Resource resource) throws IOException {
        String filename = resource.getFilename();
        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
            // 处理application.xml
            return (Map) PropertiesLoaderUtils.loadProperties(resource);
        }
        // 处理application.properties
        return new OriginTrackedPropertiesLoader(resource).load();
    }
}
YamlPropertySourceLoader 类

负责处理yml、yaml配置文件

public class YamlPropertySourceLoader implements PropertySourceLoader {

    @Override
    public String[] getFileExtensions() {
        return new String[] { "yml", "yaml" };
    }

    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
            throw new IllegalStateException(
                "Attempted to load " + name + " but snakeyaml was not found on the classpath");
        }
        List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
        if (loaded.isEmpty()) {
            return Collections.emptyList();
        }
        List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
        for (int i = 0; i < loaded.size(); i++) {
            String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
            propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
                                                                   Collections.unmodifiableMap(loaded.get(i)), true));
        }
        return propertySources;
    }

}

void load()方法

void load() {
    FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
            (defaultProperties) -> {
                // 创建默认的profile链表
                this.profiles = new LinkedList<>();
                // 创建已经处理过的profile类别
                this.processedProfiles = new LinkedList<>();
                // 默认设置为未激活
                this.activatedProfiles = false;
                // 创建load对象
                this.loaded = new LinkedHashMap<>();
                // 加载配置profile信息,默认未default
                initializeProfiles();
                // 遍历Profiles,并加载解析
                while (!this.profiles.isEmpty()) {
                    // 从双向链表中获取一个profile对象
                    Profile profile = this.profiles.poll();
                    // 非默认的就加入,进去看源码即可
                    if (isDefaultProfile(profile)) {
                        addProfileToEnvironment(profile.getName());
                    }
                    load(profile, this::getPositiveProfileFilter,
                            addToLoaded(MutablePropertySources::addLast, false));
                    this.processedProfiles.add(profile);
                }
                // 解析profile
                load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                // 加载默认的属性文件application.properties
                addLoadedPropertySources();
                applyActiveProfiles(defaultProperties);
            });
}

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    // 获得默认的扫描路径,如果没有特殊指定
    // 就采用DEFAULT_SEARCH_LOCATIONS中定义的4个路径
    // 而getSearchNames方法获得的就是application这个默认的配置文件名
    // 然后逐一遍历加载目录路径及其指定文件名的文件
    // file:./config/file:./classpath:/config/ classpath:/ 默认四个路径
    getSearchLocations().forEach((location) -> {
        boolean isFolder = location.endsWith("/");
        // 去对应的路径下获取属性文件,默认的文件名称是application
        Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
        names.forEach((name) -> load(location, name, profile, filterFactory, 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)) {
                // 去对应的文件夹下加载属性文件,application.properties application.yml
                load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
                return;
            }
        }
        throw new IllegalStateException("File extension of config file location '" + location
                + "' is not known to any PropertySourceLoader. If the location is meant to reference "
                + "a directory, it must end in '/'");
    }
    Set<String> processed = new HashSet<>();
    // 获取properties和yml的资源加载器
    for (PropertySourceLoader loader : this.propertySourceLoaders) {
        // 获取对于应的加载器的后缀 properties、xml、yml、yaml
        for (String fileExtension : loader.getFileExtensions()) {
            if (processed.add(fileExtension)) {
                // 加载文件 application.properties application.yml
                // application.yml application.yaml
                loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
                        consumer);
            }
        }
    }
}

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
        Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
    DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
    if (profile != null) {
        // 如果有profile的情况比如dev --> application-dev.properties
        // Try profile-specific file & profile section in profile file (gh-340)
        String profileSpecificFile = prefix + "-" + profile + fileExtension;
        load(loader, profileSpecificFile, profile, defaultFilter, consumer);
        load(loader, profileSpecificFile, profile, profileFilter, consumer);
        // Try profile specific sections in files we've already processed
        for (Profile processedProfile : this.processedProfiles) {
            if (processedProfile != null) {
                String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
                load(loader, previouslyLoaded, profile, profileFilter, consumer);
            }
        }
    }
    // 加载正常的情况的属性文件 application.properties
    // Also try the profile-specific section (if any) of the normal file
    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);
    }
}

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
        throws IOException {
    // 文档缓存的主键对象
    DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
    // 获取缓存数据
    List<Document> documents = this.loadDocumentsCache.get(cacheKey);
    if (documents == null) {
        // 加载属性文件信息
        List<PropertySource<?>> loaded = loader.load(name, resource);
        documents = asDocuments(loaded);
        this.loadDocumentsCache.put(cacheKey, documents);
    }
    return documents;
}

到此位置loader.load(name, resource);这个方法就会触发到PropertiesPropertySourceLoader或者YamlPropertySourceLoader的load方法,去加载配置文件的内容。