SpringBoot 启动分析(三) — Environment 的初始化流程

栏目: 编程语言 · XML · 发布时间: 4年前

内容简介:比较重要的获取本机的 主机名和IP地址,封装在

1. Environment 的初始化流程

ConfigFileApplicationListener 收到 ApplicationEnvironmentPreparedEvent 事件后通过 SPI 加载所有的 EnvironmentPostProcessor 实现,触发其 postProcessEnviroment 方法。

SpringApplication.run() ->
SpringFactoriesLoader.loadFactories(ApplicationListener) ->
SpringApplication.prepareEnviroment() -> EventPublishingRunListener.enviromentPrepared(ApplicationEnviromentPraparedEvent) ->
SimpleApplicationEventMulticaster.multicastEvent() ->
ConfigFileApplicationListener.onApplicationOnEnviromentPreparedEvent() ->
EnviromentPostProcessor.postProcessEnviroment()

比较重要的 EnviromentPostProcessor 实现是 HostInfoEnvironmentPostProcessorConfigFileApplicationListener

2. HostInfoEnvironmentPostProcessor.postProcessEnviroment

获取本机的 主机名和IP地址,封装在 PropertySource 添加到 environment 里。

3. ConfigFileApplicationListener.postProcessEnviroment

ConfigFileApplicationListener 自身也实现了 EnvironmentPostProcessor ,通过内部类 Loader 去加载配置文件,其主要流程如下:

  1. 从 Environment 中获取 active 和 include 的 profile 集合。进行迭代:
  2. 获取所有的搜索路径,进行迭代,默认的搜索路径是 classpath:/,classpath:/config/,file:./,file:./config/
  3. 如果某个搜索路径不以 / 结尾的则认为是一个文件,直接加载,否则,找出所有的搜索文件名 name 进行迭代搜索,默认的搜索文件名是 “application”。
  4. 通过 PropertySourcesLoader 找出支持的所有配置文件后缀进行迭代。
  5. 最终得到 location + name + "-" + profile + "." + ext 组成的一个具体的完整路径,通过 PropertiesLoader.load 方法加载该路径指向的配置文件。
  6. PropertiesLoader.load 内部又根据配置文件的后缀用不同的 PropertySourceLoader 去加载得到一个 PropertySource
  7. 对于解析得到的 PropertySource ,找出里面激活的 profile,添加到 proflie 集合里进行迭代。
  8. 继续迭代下一个 profile 。

3.1 PropertySourceLoader

PropertySourceLoader 是用来加载 PropertySource 的一个策略接口,有两个具体的实现类 PropertiesPropertySourceLoaderYamlPropertySourceLoader ,前者用于加载 properties/xml 后缀的配置文件,后者用于加载 yml 后者的配置文件。

3.2 PropertySourcesLoader

PropertySourcesLoader 是一个 facade 类,通 SpringFactoriesLoader 加载 PropertySourceLoader 的所有实现类。在它的 load 方法里会迭代这些实现类以加载特定后缀的配置文件。

public PropertySourcesLoader(MutablePropertySources propertySources) {
    Assert.notNull(propertySources, "PropertySources must not be null");
    this.propertySources = propertySources;
    this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
}

public PropertySource<?> load(Resource resource, String group, String name,
        String profile) throws IOException {
    if (isFile(resource)) {
        String sourceName = generatePropertySourceName(name, profile);

        for (PropertySourceLoader loader : this.loaders) {
            if (canLoadFileExtension(loader, resource)) {
                PropertySource<?> specific = loader.load(sourceName, resource, profile);
                addPropertySource(group, specific);
                return specific;
            }
        }
    }
    return null;
}

private void addPropertySource(String basename, PropertySource<?> source) {
    if (source == null) {
        return;
    }
    if (basename == null) {
        this.propertySources.addLast(source);
        return;
    }

    EnumerableCompositePropertySource group = getGeneric(basename);
    group.add(source);
    logger.trace("Adding PropertySource: " + source + " in group: " + basename);

    if (this.propertySources.contains(group.getName())) {
        // 替换原有的
        this.propertySources.replace(group.getName(), group);
    } else {
        // 把最新的添加到列表的首部
        // 对于 PropertiesPropertySourceLoader, properties 后缀的比 xml 的先加载,优先级反而低了
        this.propertySources.addFirst(group);
    }
}

3.3 Loader 加载配置的源码

// 加载属性源到 enviroment
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    // 把随机值的属性源添加到 enviroment
    RandomValuePropertySource.addToEnvironment(environment);

    // 从配置文件加载属性源到 environment 
    new Loader(environment, resourceLoader).load();
}

// Loader 类
public void load() {
    this.propertiesLoader = new PropertySourcesLoader();
    this.activatedProfiles = false;
    this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
    this.processedProfiles = new LinkedList<Profile>();

    // Pre-existing active profiles set via Environment.setActiveProfiles()
    // are additional profiles and config files are allowed to add more if
    // they want to, so don't call addActiveProfiles() here.
    // 添加已存在、激活的 profiles
    Set<Profile> initialActiveProfiles = initializeActiveProfiles();
    this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
    if (this.profiles.isEmpty()) {
        for (String defaultProfileName : this.environment.getDefaultProfiles()) {
            Profile defaultProfile = new Profile(defaultProfileName, true);
            if (!this.profiles.contains(defaultProfile)) {
                this.profiles.add(defaultProfile);
            }
        }
    }

    // 迭代过程中默认的 proflie 用 null 表示。添加到最后可以第一个出队列。
    // 后面迭代的激活的 profiles 会覆写默认的配置
    this.profiles.add(null);

    // 迭代 proflie
    while (!this.profiles.isEmpty()) {
        Profile profile = this.profiles.poll();
        // 迭代要搜索的路径
        for (String location : getSearchLocations()) {
            if (!location.endsWith("/")) {
                // location is a filename already, so don't search for more filenames
                load(location, null, profile);
            } else {
                // 迭代要搜索的配置文件名
                for (String name : getSearchNames()) {
                    load(location, name, profile);
                }
            }
        }
        this.processedProfiles.add(profile);
    }

    // 把加载到的 PropertySources 添加到 enviroment
    addConfigurationProperties(this.propertiesLoader.getPropertySources());
}

private void load(String location, String name, Profile profile) {
    String group = "profile=" + ((profile != null) ? profile : "");
    if (!StringUtils.hasText(name)) {
        // Try to load directly from the location
        loadIntoGroup(group, location, profile);
    }
    else {
        // 迭代所有支持的文件后缀
        for (String ext : this.propertiesLoader.getAllFileExtensions()) {
            if (profile != null) {
                // 尝试 profile 特定的文件,文件名包含 proflie 值的
                loadIntoGroup(group, location + name + "-" + profile + "." + ext, null);
                for (Profile processedProfile : this.processedProfiles) {
                    if (processedProfile != null) {
                        loadIntoGroup(group, location + name + "-" + processedProfile + "." + ext, profile);
                    }
                }
                // Sometimes people put "spring.profiles: dev" in
                // application-dev.yml (gh-340). Arguably we should try and error
                // out on that, but we can be kind and load it anyway.
                loadIntoGroup(group, location + name + "-" + profile + "." + ext, profile);
            }
            // Also try the profile-specific section (if any) of the normal file
            loadIntoGroup(group, location + name + "." + ext, profile);
        }
    }
}

// 如果解析的配置文件里用 spring.config.location 指定了新的位置,
// 那么下一轮查找也把 spring.config.location 属性指定的位置加入搜索范围
// 默认的搜索位置有: classpath:/,classpath:/config/,file:./,file:./config/
private Set<String> getSearchLocations() {
    Set<String> locations = new LinkedHashSet<String>();
    // User-configured settings take precedence, so we do them first
    if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
        for (String path : asResolvedSet(
                this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
            if (!path.contains("$")) {
                path = StringUtils.cleanPath(path);
                if (!ResourceUtils.isUrl(path)) {
                    path = ResourceUtils.FILE_URL_PREFIX + path;
                }
            }
            locations.add(path);
        }
    }

    // DEFAULT_SEARCH_LOCATIONS:Note the order is from least to most specific (last one wins)
    // asResolvedSet 会进行逆序操作
    locations.addAll(asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
                    DEFAULT_SEARCH_LOCATIONS));
    return locations;
}

// 如果有、则用 spring.config.name 属性指定配置文件名,
// 否则用默认的配置文件名是 application
private Set<String> getSearchNames() {
    if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
        return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null);
    }
    return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}

private Set<String> asResolvedSet(String value, String fallback) {
    List<String> list = Arrays.asList(StringUtils.trimArrayElements(
            StringUtils.commaDelimitedListToStringArray(value != null
                    ? this.environment.resolvePlaceholders(value) : fallback)));
    Collections.reverse(list);
    return new LinkedHashSet<String>(list);
}

// load 方法会调用到这里来加载属性源,删除了一些trace日志相关的代码
private PropertySource<?> doLoadIntoGroup(String identifier, String location,
        Profile profile) throws IOException {
    Resource resource = this.resourceLoader.getResource(location);
    PropertySource<?> propertySource = null;
    if (resource != null && resource.exists()) {
        String name = "applicationConfig: [" + location + "]";
        String group = "applicationConfig: [" + identifier + "]";

        // propertiesLoader.load 使用 PropertiesPropertySourceLoader/YamlPropertySourceLoader 对资源进行加载
        propertySource = this.propertiesLoader.load (resource, group, name,
                (profile == null ? null : profile.getName()));
        if (propertySource != null) {
            handleProfileProperties(propertySource);
        }
    }
    return propertySource;
}

private void addConfigurationProperties(MutablePropertySources sources) {
    List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();
    for (PropertySource<?> item : sources) {
        reorderedSources.add(item);
    }
    addConfigurationProperties(new ConfigurationPropertySources(reorderedSources));
}

private void addConfigurationProperties(ConfigurationPropertySources configurationSources) {
    MutablePropertySources existingSources = this.environment.getPropertySources();
    if (existingSources.contains(DEFAULT_PROPERTIES)) {
        // 覆盖默认的属性源
        existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);
    } else {
        // 前面加载的比后面加载的优先级高
        existingSources.addLast(configurationSources);
    }
}

不同的搜索、加载顺序决定了配置文件的不同优先级:

  1. 所有配置文件的配置都比默认配置的优先级高;
  2. 先加载的比后加载的优先级高;
  3. 对于 PropertiesPropertySourceLoader 加载同一个文件名, properties 后缀的比 xml 的先加载,优先级反而低了。

欢迎关注我的微信公众号: coderbee笔记 ,可以更及时回复你的讨论。

SpringBoot 启动分析(三) — Environment 的初始化流程

以上所述就是小编给大家介绍的《SpringBoot 启动分析(三) — Environment 的初始化流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

来吧!带你玩转 Excel VBA

来吧!带你玩转 Excel VBA

罗刚君、杨嘉恺 / 电子工业出版社 / 2013-7 / 85.00元

本书旨在普及Excel VBA 基础理论,以及通过VBA 的高级应用扩展Excel 的功能,提升读者的制表效率,解决工作中的疑难,同时亦可借此开发商业插件。 本书主要分为操作自动化引言篇、入门篇、进阶篇和疑难解答篇,覆盖从入门到提高的所有内容,以满足不同层次的读者需求。其中操作自动化引言篇简述了操作自动化的需求与方式,借此引出VBA 入门篇。VBA 入门篇包含第2 章到第13 章,主要介绍了......一起来看看 《来吧!带你玩转 Excel VBA》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具