SpringBoot实战分析(四)环境配置与YML加载

入口

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

断点跟踪

1.准备环境

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 创建和配置环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

2.创建和配置环境

创建StandardServletEnvironment对象

如果控制器类型是Servlet,直接new 一个 StandardServletEnvironment对象。

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webApplicationType == WebApplicationType.SERVLET) {
        return new StandardServletEnvironment();
    }
    return new StandardEnvironment();
}

返回值属性

配置环境

protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
   // 配置property源
    configurePropertySources(environment, args);
   // 配置profiles
    configureProfiles(environment, args);
}

//配置文件什么都不写默认情况,此方法不会做任何操作
protected void configurePropertySources(ConfigurableEnvironment environment,
        String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(
                new MapPropertySource("defaultProperties", this.defaultProperties));
    }
  省略。。。。。。
}
//为整个application配置活跃的环境
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    // 确保初始化环境
    environment.getActiveProfiles(); 
    Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
   // 赋值
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

看上面的getActiveProfiles方法,获取activiti的属性值
// 获取activit的profiles 比如spring.profiles.activite = dev
protected Set<String> doGetActiveProfiles() {
   // 同步activeProfiles
    synchronized (this.activeProfiles) {
        //判空操作
        if (this.activeProfiles.isEmpty()) {
            // get  spring.profiles.active属性的值
            String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
            // 是否有值
            if (StringUtils.hasText(profiles)) {
                setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                        StringUtils.trimAllWhitespace(profiles)));
            }
        }
        return this.activeProfiles;
    }
}

//把activit的属性值 赋给ConfigurableEnvironment
public void setActiveProfiles(String... profiles) {
    Assert.notNull(profiles, "Profile array must not be null");
    synchronized (this.activeProfiles) {
        this.activeProfiles.clear();
        for (String profile : profiles) {
            validateProfile(profile);
            this.activeProfiles.add(profile);
        }
    }
}


给listeners添加当前的environmen

public void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}


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

@Override
public void multicastEvent(ApplicationEvent event) {
    // 组播事件
    multicastEvent(event, resolveDefaultEventType(event));
}

在multicastEvent中,会调用一个叫做invokeListener(listener, event);方法,这个后边的流程会涉及到2个重点类AbstractEnvironment,ConfigFileApplicationListener

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
           // 重点方法 调用监听器监听事件
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        // 同上
        doInvokeListener(listener, event);
    }
}

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
       // 重点 这方法会调用ConfigFileApplicationListener中的方法 
        listener.onApplicationEvent(event);
    }
   省略。。。。。。
}

看onApplicationEnvironmentPreparedEvent方法,调用ConfigFileApplicationListener中的postProcessEnvironment方法

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }
    省略。。。。。。
}

for (EnvironmentPostProcessor postProcessor : postProcessors) {
    postProcessor.postProcessEnvironment(event.getEnvironment(),
            event.getSpringApplication());
}
加载Yml配置文件中的profiles的属性

关键点,在中的addPropertySources方法,这个方法中会有一个load的操作

public void postProcessEnvironment(ConfigurableEnvironment environment,
        SpringApplication application) {
    addPropertySources(environment, application.getResourceLoader());
}

new Loader(environment, resourceLoader).load();

load中调用一个初始化的方法,看到图中各个参数的值,第一调用会获取上文中的默认的属性值default。

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        Profile profile = this.profiles.poll();
       // 加载
        load(profile, this::getPositiveProfileFilter,
                addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    load(null, this::getNegativeProfileFilter,
            addToLoaded(MutablePropertySources::addFirst, true));
    addLoadedPropertySources();
}

上面①的调用会经过下面这个流程,主要方法都加粗显示

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) {
    省略。。。。。。
    for (PropertySourceLoader loader : this.propertySourceLoaders) {
        for (String fileExtension : loader.getFileExtensions()) {
            String prefix = location + name;
            fileExtension = "." + fileExtension;
            loadForFileExtension(loader, prefix, fileExtension, profile,
                    filterFactory, consumer);
        }
    }
}
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
        String fileExtension, Profile profile,
        DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    省略。。。。。。
    // Also try the profile-specific section (if any) of the normal file
    load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

这个方法是最后的load,主要作用加载

application.propertites

application.xml

applicaiton.yaml

application.yml 这四种文件 

private void load(PropertySourceLoader loader, String location, Profile profile,
        DocumentFilter filter, DocumentConsumer consumer) {
    try {
         // 读取文件
        Resource resource = this.resourceLoader.getResource(location);
        String description = getDescription(location, resource);
        if (profile != null) {
            description = description + " for profile " + profile;
        }
        // 如果读取的文件不存在,直接return,从调用方法进行下一个文件循环
       // 如果当前文件夹的文件都扫描完成,直接扫面下一个文件夹
        if (resource == null || !resource.exists()) {
            this.logger.trace("Skipped missing config " + description);
            return;
        }
        if (!StringUtils.hasText(
                StringUtils.getFilenameExtension(resource.getFilename()))) {
            this.logger.trace("Skipped empty config extension " + description);
            return;
        }
        String name = "applicationConfig: [" + location + "]";
        List<Document> documents = loadDocuments(loader, name, resource);
        if (CollectionUtils.isEmpty(documents)) {
            this.logger.trace("Skipped unloaded config " + description);
            return;
        }
        List<Document> loaded = new ArrayList<>();
        for (Document document : documents) {
            if (filter.match(document)) {
                addActiveProfiles(document.getActiveProfiles());
                addProfiles(document.getIncludeProfiles());
                loaded.add(document);
            }
        }
        Collections.reverse(loaded);
        if (!loaded.isEmpty()) {
            loaded.forEach((document) -> consumer.accept(profile, document));
            this.logger.debug("Loaded config file " + description);
        }
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to load property "
                + "source from location '" + location + "'", ex);
    }
}

读取文件顺序:

第一个文件夹:

第一步扫描:URL [file:./config/application.properties]

判断resource,不存在就执行下一个文件扫描


第二步扫描:URL [file:./config/application.xml]

操作同上。

第三步扫描:URL [file:./config/application.yml]

操作同上。

第四步扫描:URL [file:./config/application.yaml]

操作同上。


第二个文件夹:

扫描路径:URL [file:./application.properties]

顺序和文件名同上。


第三个文件夹:

扫描路径:class path resource [config/application.properties]

|| 等同

classpath:/config/application.properties(加上了环境变量路径)

顺序和文件名同上。


第四个文件夹:

扫描路径:classpath:/application.properties(现在的路径指打了resources这个项目配置路径)

顺序和文件名同上。


    因为项目是applicaiton.yml配置文件,所以到第四步会出现不同的情况,会扫描到该文件。

就会读取文件中的active的属性值:dev


当读取到dev的时候,会调用load中的这个循环,

for (Document document : documents) {
    if (filter.match(document)) {
        addActiveProfiles(document.getActiveProfiles());
        addProfiles(document.getIncludeProfiles());
        loaded.add(document);
    }
}

调用2个方法,分别是addActiveProfiles和addProfiles,第一方法把获取到的profiles

添加到一个set集合中。第二个方法把获取到的profiles添加到当前类的LinkedList<Profile> profiles中。

void addActiveProfiles(Set<Profile> profiles) {
    if (this.activatedProfiles || profiles.isEmpty()) {
        return;
    }
    addProfiles(profiles);
    this.logger.debug("Activated activeProfiles "
            + StringUtils.collectionToCommaDelimitedString(profiles));
    this.activatedProfiles = true;
    //移除未装配的默认配置属性,加载当前获取的属性
    removeUnprocessedDefaultProfiles();
}

addProfiles调用addProfileToEnvironment方法,向当前的environment添加profiles。


接着上面扫描的文件说,即使扫描到可用的文件,也会在添加属性之后,继续扫描解下来的文件。会一直扫描完,


扫面完主配置之后,因为环境是分类似于生产测试之类的,所以在profiles=dev的时候,会继续去扫描application-dev.properties这样的文件配置,扫描路径和顺序都不变,只不过文件名后面会加上 -dev的标识。会

load()方法在while循环load之后还会再次调用load方法,把加载出来的配置以最高优先级去加载,去除未激活的配置,到这里基本就算完成了加载配置文件的步骤了。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章