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方法,把加載出來的配置以最高優先級去加載,去除未激活的配置,到這裏基本就算完成了加載配置文件的步驟了。




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