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

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

内容简介:比较重要的获取本机的 主机名和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 的初始化流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

R Cookbook

R Cookbook

Paul Teetor / O'Reilly Media / 2011-3-22 / USD 39.99

With more than 200 practical recipes, this book helps you perform data analysis with R quickly and efficiently. The R language provides everything you need to do statistical work, but its structure ca......一起来看看 《R Cookbook》 这本书的介绍吧!

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

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换