Spring Boot 启动(二) Environment 加载

Spring Boot 启动(二) Environment 加载

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

上一节中讲解了 SpringApplication 启动的整个流程,本节关注第二步 prepareEnvironment,尤其是配置文件的加载。

一、prepareEnvironment 加载流程分析

public ConfigurableApplicationContext run(String... args) {
    // 1. listeners 用户监听容器的运行,默认实现为 EventPublishingRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

    // 2. 初始化环境变量 environment
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
}

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 1. 根据 webApplicationType 创建相应的 Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 2. 配置 Environment,主要有三点:一是 ConversionService;二是数据源,包括命令行参数;三是 Profiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 3. 激活 environmentPrepared 事件,主要是加载 application.yml 等配置文件
    //    ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    // ??? 以后再研究
    ConfigurationPropertySources.attach(environment);
    return environment;
}
  1. 根据 webApplicationType 类型创建相应的 Environment,分为 StandardEnvironment、StandardServletEnvironment、StandardReactiveWebEnvironment。

  2. configureEnvironment 主要有三点:一是 ConversionService;二是数据源,包括命令行参数;三是 Profiles

  3. 激活 environmentPrepared 事件,主要是加载 application.yml 等配置文件

2.1 getOrCreateEnvironment

对于 StandardServletEnvironment 的 servletContextInitParams 和 servletConfigInitParams 两个 web 的数据源,会先用 StubPropertySource 占位,等初始化 web 容器时再替换。详见:https://www.cnblogs.com/binarylei/p/10291323.html

2.2 configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
    // 1. 设置 ConversionService
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    // 2. 加载 defaultProperties 和 CommandLinePropertySource(main 参数) 信息
    configurePropertySources(environment, args);
    // 3. 设置 environment 的 Profiles(additionalProfiles + spring.profile.active/default)
    configureProfiles(environment, args);
}
  1. configurePropertySources 添加在 Spring Framework 基础上添加了两个新的据源,一是自定义的 defaultProperties;二是 CommandLinePropertySource(main 参数)

  2. configureProfiles 在原有的剖面上添加自定义的剖面 additionalProfiles,注意 additionalProfiles 在前,Spring Framework 默认的剖面在后。

2.3 environmentPrepared

listeners.environmentPrepared(environment) 主要是加载配置文件,其中 listeners 是通过 spring.factories 配置的 SpringApplicationRunListener,默认实现是 EventPublishingRunListener。

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
            this.application, this.args, environment));
}

environmentPrepared 触发了 ApplicationEnvironmentPreparedEvent 事件,这个事件是在 spring.factories 配置的监听器 ConfigFileApplicationListener 处理的。

二、ConfigFileApplicationListener

2.1 ConfigFileApplicationListener 处理流程

public class ConfigFileApplicationListener
        implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
                @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 1. Environment 加载完成触发 ApplicationEnvironmentPreparedEvent
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        // 2. ApplicationContext 加载完成触发 ApplicationPreparedEvent
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }
}

本例中触发了 ApplicationEnvironmentPreparedEvent 事件。

private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {
    // 1. 委托给 EnvironmentPostProcessor 处理,也是通过 spring.factories 配置
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    // 2. ConfigFileApplicationListener 本身也实现了 EnvironmentPostProcessor 接口
    postProcessors.add(this);
    // 3. spring 都都通过 AnnotationAwareOrderComparator 控制执行顺序
    AnnotationAwareOrderComparator.sort(postProcessors);
    // 4. 执行 EnvironmentPostProcessor
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}

我们重点关注的是 ConfigFileApplicationListener 是如何加载配置文件的,其它的 EnvironmentPostProcessor 暂时忽略。跟踪 ConfigFileApplicationListener#postProcessEnvironment 方法,最终加载配置文件委托给了其内部类 Loader 完成。

protected void addPropertySources(ConfigurableEnvironment environment,
        ResourceLoader resourceLoader) {
    // 1. 加载随机数据源 ${random.int} ${random.long} ${random.uuid}
    RandomValuePropertySource.addToEnvironment(environment);
    // 2. 加载配置文件
    new Loader(environment, resourceLoader).load();
}

三、Loader 加载配置文件

3.1 Spring Boot 默认目录及配置文件名

Spring Boot 默认的配置文件的目录及配置文件名称如下:

// 1. 配置文件默认的目录,解析时会倒置,所以 Spring Boot 默认 jar 包的配置文件会覆盖 jar 中的配置文件
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";

// 2. 配置文件默认的文件名
private static final String DEFAULT_NAMES = "application";
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";

3.2 profiles 解析配置文件的顺序

先了解一起 Spring FrameWork 和 Spring Boot 的 profiles 的概念。

配置 Spring 说明
spring.profiles.active Spring FrameWork AbstractEnvironment 激活的剖面
spring.profiles.default Spring FrameWork AbstractEnvironment 默认剖面
additionalProfiles Spring Boot SpringApplication 自定义激活的剖面
spring.profiles.include Spring Boot ConfigFileApplicationListener 自定义激活的剖面

在启动 SpringApplication#prepareEnvironment 时已经激活了 additionalProfiles + Spring FrameWork 剖面,注意剖面的顺序。 ConfigFileApplicationListener 引入 spring.profiles.include

private void initializeProfiles() {
    // 1. null
    this.profiles.add(null);
    // spring.profiles.include + spring.profiles.active 配置的剖面
    Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
    // 2. environment.getActiveProfiles() 过滤 activatedViaProperty 之后的剖面
    //    目前看只有 SpringApplication 配置的 additionalProfiles
    this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
    // 3. spring.profiles.include + spring.profiles.active
    //    addActiveProfiles 方法只能调用一次,前提是 activatedViaProperty 不为空
    addActiveProfiles(activatedViaProperty);
    // 4. spring.profiles.default
    if (this.profiles.size() == 1) {
        for (String defaultProfileName : this.environment.getDefaultProfiles()) {
            Profile defaultProfile = new Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }
}

Spring Boot 配置文件 Profiles(application-dev.properties) 的解析顺序如下:

  1. 首先解析 null,也就是 application.properties 或 application.yml 文件
  2. spring.profiles.include/active 属性配置之外的剖面先解析,一般是 activatedViaProperty 或其它编程式配置的 Profiles
  3. spring.profiles.include 定义的剖面,第三和第四的顺序在 getProfilesActivatedViaProperty 中定义
  4. spring.profiles.active 定义的剖面
  5. spring.profiles.default 如果没有激活的剖面,默认 default,即没有 2、3、4 项

注意:实际读取配置文件的顺序和解析的相反,下面会详细说明。 为什么要这么设计???

还有一种情况是在配置文件 application.properties 中定义了 spring.profiles.include/active 属性的情况。加载到对应的配置文件后需要判断是否定义了以上两个属性,如果定义了,也需要加载譔剖面对应的配置文件。

private void load(PropertySourceLoader loader, String location, Profile profile,
        DocumentFilter filter, DocumentConsumer consumer) {
    List<Document> loaded = new ArrayList<>();
    for (Document document : documents) {
        if (filter.match(document)) {
            // 1. spring.profiles.active,如果已经定义了该方法就不会再执行了
            addActiveProfiles(document.getActiveProfiles());
            // 2. spring.profiles.include
            addIncludedProfiles(document.getIncludeProfiles());
            loaded.add(document);
        }
    }
}

// 毫无疑问,如果配置文件中定义了 spring.profiles.include 则需要先解析这些剖面,再解析其余的剖面
private void addIncludedProfiles(Set<Profile> includeProfiles) {
    LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
    this.profiles.clear();
    // 1. 先解析配置文件中定义的 spring.profiles.include,当然如果已经解析了则需要移除
    this.profiles.addAll(includeProfiles);
    this.profiles.removeAll(this.processedProfiles);
    // 2. 再解析剩余的剖面
    this.profiles.addAll(existingProfiles);
}

3.3 配置文件解析

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 1. this.profiles 定义了 profile 解析顺序
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        Profile profile = this.profiles.poll();
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 2. 具体解析配置文件到 this.loaded 中
        load(profile, this::getPositiveProfileFilter,
                addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    resetEnvironmentProfiles(this.processedProfiles);
    // ??? 先忽略
    load(null, this::getNegativeProfileFilter,
            addToLoaded(MutablePropertySources::addFirst, true));
    // 3. 加载配置文件到 environment 中,注意读取配置文件的顺序和解析的相反
    addLoadedPropertySources();
}
  1. initializeProfiles 加载所有的剖面,解析时会按上面提到的顺序进行解析
  2. load 具体解析配置文件到 this.loaded 中
  3. addLoadedPropertySources 加载配置文件到 environment 中,注意读取配置文件的顺序和解析的相反

每天用心记录一点点。内容也许不重要,但习惯很重要!

猜你喜欢

转载自www.cnblogs.com/binarylei/p/10634387.html