【源码分析-Spring Boot】-5.Spring Boot Profile 是如何生效的


Spring Boot Profile 环境隔离: 【从零开始学Spring Boot】-5.Spring Boot Profile环境隔离

1.Spring Boot 中 Profile 是如何生效的?

在启动类启动的时候,按照如下顺序调用 SpringApplication.run -> prepareEnvironment ->

listeners.environmentPrepared -> listener.environmentPrepared -> initialMulticaster.multicastEvent

-> getApplicationListeners -> invokeListener -> doInvokeListener -> listener.onApplicationEvent

在 ApplicationListener 中,有一个 ConfigFileApplicationListener,这个监听器用来解析配置文件,所以会调用它的 onApplicationEvent 方法,它的实现如下

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

启动时这里的 event 是 ApplicationEnvironmentPreparedEvent,所以会执行 onApplicationEnvironmentPreparedEvent 方法

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    // 从 spring.factories 中获取 EnvironmentPostProcessor
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    // 将当前 ConfigFileApplicationListener 加入到 postProcessors 中
    postProcessors.add(this);
    // 根据 @Ordered 配置的顺序进行排序
    AnnotationAwareOrderComparator.sort(postProcessors);
    // 触发 postProcessEnvironment 方法
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}

ConfigFileApplicationListener 的 postProcessEnvironment 实现如下

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    // 添加配置文件
    addPropertySources(environment, application.getResourceLoader());
}

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    // 加载配置文件
    new Loader(environment, resourceLoader).load();
}

Loader 的构造函数中,使用 SpringFactoriesLoader 加载 PropertySourceLoader,它有两个实现类:PropertiesPropertySourceLoader 和 YamlPropertySourceLoader,前者解析 .properties 和 .xml,后者解析 .yml 和 .yaml,在 spring.factories 中,PropertiesPropertySourceLoader 在前,所以先解析 .properties 文件,YamlPropertySourceLoader中 .yml 在前,先解析 .yml

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    this.environment = environment;
    this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
    this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
    this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
                                                                     getClass().getClassLoader());
}

在 load() 方法中,获取所有的 profiles,然后通过 load 方法记在对应的配置文件

void load() {
    FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY, (defaultProperties) -> {
        this.profiles = new LinkedList<>();
        this.processedProfiles = new LinkedList<>();
        this.activatedProfiles = false;
        this.loaded = new LinkedHashMap<>();
        // 初始化 Profiles
        initializeProfiles();
        while (!this.profiles.isEmpty()) {
            Profile profile = this.profiles.poll();
            if (isDefaultProfile(profile)) {
                addProfileToEnvironment(profile.getName());
            }
            // 加载配置文件
            load(profile, this::getPositiveProfileFilter,
                 addToLoaded(MutablePropertySources::addLast, false));
            this.processedProfiles.add(profile);
        }
        load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
        addLoadedPropertySources();
        applyActiveProfiles(defaultProperties);
    });
}

initializeProfiles()

private void initializeProfiles() {
    // The default profile for these purposes is represented as null. We add it
    // first so that it is processed first and has lowest priority.
    this.profiles.add(null);
    // 获取 spring.profiles.active 值
    Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
    // 获取 spring.profiles.include 值
    Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
    // 从其他途径获取 profile
    List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
    this.profiles.addAll(otherActiveProfiles);
    // Any pre-existing active profiles set via property sources (e.g.
    // System properties) take precedence over those added in config files.
    this.profiles.addAll(includedViaProperty);
    addActiveProfiles(activatedViaProperty);
    if (this.profiles.size() == 1) { // only has null profile
        for (String defaultProfileName : this.environment.getDefaultProfiles()) {
            Profile defaultProfile = new Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }
}

load()

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    getSearchLocations().forEach((location) -> {
        boolean isFolder = location.endsWith("/");
        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)) {
                // 加载配置文件
                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<>();
    for (PropertySourceLoader loader : this.propertySourceLoaders) {
        for (String fileExtension : loader.getFileExtensions()) {
            if (processed.add(fileExtension)) {
                loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer);
            }
        }
    }
}

文章作者: Soulballad
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Soulballad !
评论
 上一篇
【从零开始学Spring Boot】-6.Spring Boot Banner自定义 【从零开始学Spring Boot】-6.Spring Boot Banner自定义
1.简介1.1 概述 The banner that is printed on start up can be changed by adding a banner.txt file to your classpath or by set
下一篇 
【从零开始学Spring Boot】-5.Spring Boot Profile环境隔离 【从零开始学Spring Boot】-5.Spring Boot Profile环境隔离
1.简介1.1 概述 Spring Profiles provide a way to segregate parts of your application configuration and make it be available o
  目录