springboot啓動流程(四)application配置文件加載過程

全部文章

http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml

 

觸發監聽器加載配置文件

上一篇文章中,咱們看到了Environment對象的建立方法。同時也稍微說起了一下ConfigFileApplicationListener這個監聽器,這個監聽器主要工做是爲了加載application.properties/yml配置文件的。spring

回顧一下prepareEnvironment方法的代碼springboot

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments
        ) {
    // 建立一個Environment對象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置Environment對象
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 觸發監聽器(主要是觸發ConfigFileApplicationListener,這個監聽器將會加載如application.properties/yml這樣的配置文件)
    listeners.environmentPrepared(environment);
    // 省略
}

咱們看到Environment對象在初始建立並配置以後會發布出一個事件給監聽器,注意!這裏的監聽器並非ConfigFileApplicationListener而是一個負責分發事件的監聽器EventPublishingRunListener。app

 

咱們跟進EventPublishingRunListener監聽器的environmentPrepared方法dom

private final SimpleApplicationEventMulticaster initialMulticaster;

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

這裏包裝了一個ApplicationEnvironmentPreparedEvent事件,並經過廣播的方式廣播給監聽該事件的監聽器,到這個時候才觸發了ConfigFileApplicationListeneride

 

咱們跟進ConfigFileApplicationListener的onApplicationEvent方法post

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 只觸發Environment相關的事件
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

Event將會觸發onApplicationEnvironmentPreparedEventthis

 

繼續跟進編碼

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        // 執行後置處理器
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}

咱們看到,首先加載了Environment的後置處理器,而後通過排序之後遍歷觸發每一個處理器。這裏注意,ConfigFileApplicationListener自己也實現了EnvironmentPostProcessor接口,因此這裏將會觸發ConfigFileApplicationListener內部方法執行spa

 

咱們跟進ConfigFileApplicationListener的postProcessEnvironment方法

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

再跟進addPropertySources方法

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    new Loader(environment, resourceLoader).load();
}

咱們看到,這裏實例化了一個Loader用來加載application配置文件,而核心邏輯就在load方法當中。

 

加載器加載application配置文件

跟進Loader加載器的構造方法中

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    this.environment = environment;
    this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
    this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
    // 文件application配置文件的資源加載器,包括propertis/xml/yml/yaml擴展名
    this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
            getClass().getClassLoader());
}

咱們注意到,再構造方法中將會從spring.factories中加載PropertySourceLoader接口的具體實現類,具體請參閱:輔助閱讀

咱們打開spring.factories能夠看到

這裏包括兩個實現

1)PropertiesPropertySourceLoader:用於加載property/xml格式的配置文件

2) YamlPropertySourceLoader:用於加載yml/yaml格式的配置文件

到這裏,咱們能夠知道springboot支持的不一樣配置文件是經過選擇不一樣的加載器來實現

 

下面,咱們回到Loader加載器的load方法中,跟進加載的主要邏輯

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 初始化profiles
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        // 消費一個profile
        Profile profile = this.profiles.poll();
        // active的profile添加到Environment
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 加載
        load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    // 重置Environment中的profiles
    resetEnvironmentProfiles(this.processedProfiles);
    // 加載
    load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    // 添加全部properties到Environment中
    addLoadedPropertySources();
}

代碼有點小長,咱們根據如何加載默認的application.properties/yml配置文件的流程來了解一下

 

先跟進initializeProfiles方法看看若是初始化profiles

private void initializeProfiles() {
    // 第一個profile爲null,這樣能保證首個加載application.properties/yml
    this.profiles.add(null);
    Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
    this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
    addActiveProfiles(activatedViaProperty);
    // 沒有額外配置profile的時候,將使用默認的
    if (this.profiles.size() == 1) {
        for (String defaultProfileName : this.environment.getDefaultProfiles()) {
            Profile defaultProfile = new Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }
}

這裏注意兩點

1)將會首先添加一個null,保證第一次加載的是application配置

2) 其次,若是沒有配置profile,那麼使用default。注意,咱們的application配置文件還未加載,因此這裏的"沒有配置"並非指你的application配置文件中有沒有配置,而是如命令行、獲取main方法傳入等其它方法配置

咱們並未配置任何active的profile,因此這裏最終將產生一個這樣的數據

profiles=[null, "default"]

 

回到load方法中,咱們繼續往下看

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 初始化profiles
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        // 消費一個profile
        Profile profile = this.profiles.poll();
        // active的profile添加到Environment
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 加載
        load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    // 重置Environment中的profiles
    resetEnvironmentProfiles(this.processedProfiles);
    // 加載
    load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    // 添加全部properties到Environment中
    addLoadedPropertySources();
}

while循環中,首先拿到的是profile=null,而後就直接進入第二個load加載方法加載配置文件

 

咱們跟進第二個load加載方法(請注意區分load方法,後續還會出現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));
    });
}

該方法中的邏輯主要是搜索每一個位置下的每一個指定的配置文件名,並加載

 

跟進getSearchLocations看看要搜索哪些位置

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

private Set<String> getSearchLocations() {
    if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
        return getSearchLocations(CONFIG_LOCATION_PROPERTY);
    }
    Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
    locations.addAll(
            asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
    return locations;
}

很顯然,因爲咱們沒有自定義一些搜索位置,那麼默認搜索classpath:/、classpath:/config/、file:./、file:./下

 

回到第二個load方法,咱們再看看getSearchNames方法要加載哪些文件

private static final String DEFAULT_NAMES = "application";

private Set<String> getSearchNames() {
    if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
        String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
        return asResolvedSet(property, null);
    }
    return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}

類似的邏輯,最終返回默認的配置文件名application,也就是咱們最熟悉的名字

 

接下來,再回到第二個load方法,咱們能夠跟進第三個load方法了,看看如何根據locations和names來加載配置文件

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
        DocumentConsumer consumer) {
    // 省略
    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);
            }
        }
    }
}

這個load方法主要邏輯代表將會加載每一個加載器能夠支持的配置文件,在Loader初始化的時候咱們得到了兩個加載器,同時每一個加載器支持兩種格式。因此這裏的嵌套遍歷中,咱們將會嘗試加載4種配置文件,如

1)application.properties

2) application.xml

3) application.yml

4) application.yaml

 

再跟進loadForFileExtension方法,看看具體每種的加載

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
        Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
    DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
    // 當前沒有profile
    if (profile != null) {
        String profileSpecificFile = prefix + "-" + profile + fileExtension;
        load(loader, profileSpecificFile, profile, defaultFilter, consumer);
        load(loader, profileSpecificFile, profile, profileFilter, consumer);
        for (Profile processedProfile : this.processedProfiles) {
            if (processedProfile != null) {
                String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
                load(loader, previouslyLoaded, profile, profileFilter, consumer);
            }
        }
    }
    // 加載具體格式的文件
    load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

因爲當前profile=null,因此咱們直接進入第四個load方法

 

跟進第四個load方法,因爲該方法有點長,咱們省略次要的代碼

private void load(
        PropertySourceLoader loader, 
        String location, 
        Profile profile, 
        DocumentFilter filter,
        DocumentConsumer consumer
        ) {
    try {
        // 獲取資源
        Resource resource = this.resourceLoader.getResource(location);
  
        // 加載爲Document對象
        List<Document> documents = loadDocuments(loader, name, resource);
   
        List<Document> loaded = new ArrayList<>();
        // 遍歷Document集合
        for (Document document : documents) {
            if (filter.match(document)) {
                // 添加profile
                addActiveProfiles(document.getActiveProfiles());
                addIncludedProfiles(document.getIncludeProfiles());
                loaded.add(document);
            }
        }
        Collections.reverse(loaded);
        if (!loaded.isEmpty()) {
            // 回調處理每一個document
            loaded.forEach((document) -> consumer.accept(profile, document));
        }
    } catch (Exception ex) {}
}

首先配置文件會被加載爲Document這樣的內存對象,並最終回調處理。

 

這裏咱們看到回調是調用consumer這樣一個接口,咱們得回到第一個load方法,看看調用第二個load方法的時候傳入的consumer是啥

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 初始化profiles
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        // 消費一個profile
        Profile profile = this.profiles.poll();
        // active的profile添加到Environment
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 加載
        load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    // 重置Environment中的profiles
    resetEnvironmentProfiles(this.processedProfiles);
    // 加載
    load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    // 添加全部properties到Environment中
    addLoadedPropertySources();
}

咱們看到,在調用第二個load方法的時候就經過addToLoaded這個方法的執行來獲取一個consumer,用來回調處理配置文件的Document對象。

注意!在調用addToLoaded的時候經過方法引用指定了一個method,這個method將在consumer回調的內部被使用。

method=MutablePropertySources:addLast

 

跟進addToLoaded方法

private Map<Profile, MutablePropertySources> loaded;

private DocumentConsumer addToLoaded(
        BiConsumer<MutablePropertySources, 
        PropertySource<?>> addMethod,
        boolean checkForExisting) {
    return (profile, document) -> {
        // 省略
        MutablePropertySources merged = this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources());
        // 回調method
        addMethod.accept(merged, document.getPropertySource());
    };
}

咱們看到,loaded是一個profile和MutableProperySources的鍵值組合。方法邏輯中將會先獲取loaded裏面的MutablePropertySources,而後調用addLast方法將Document中的PropertySource給添加到MutablePropertySources中。

到這裏,一個application配置文件被加載到內存了。可是還沒完,前面的文章中咱們說過Environment對象是應用程序環境的抽象,包含了properties。那麼,咱們還得將這些內存中的PropertySource給添加到Environment中。

 

回到第一個load方法中

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 初始化profiles
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        // 消費一個profile
        Profile profile = this.profiles.poll();
        // active的profile添加到Environment
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 加載
        load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    // 重置Environment中的profiles
    resetEnvironmentProfiles(this.processedProfiles);
    // 加載
    load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    // 添加全部properties到Environment中
    addLoadedPropertySources();
}

咱們看到最後一行,addLoadedPropertySources方法的做用也就是將以前loaded裏面的東西給添加到Environment中

 

跟進addLoadedPropertySources方法

private void addLoadedPropertySources() {
    MutablePropertySources destination = this.environment.getPropertySources();
    List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
    // 反向排序
    Collections.reverse(loaded);
    String lastAdded = null;
    Set<String> added = new HashSet<>();
    // 遍歷loaded
    for (MutablePropertySources sources : loaded) {
        // 遍歷配置
        for (PropertySource<?> source : sources) {
            // 排重
            if (added.add(source.getName())) {
                // 添加每一個到Environment中
                addLoadedPropertySource(destination, lastAdded, source);
                lastAdded = source.getName();
            }
        }
    }
}

這個方法很清晰地代表,將loaded中地PropertySource給追加到Environment中。

到這裏,默認的application.properties/yml這樣地配置文件就被加載到了Environment當中了。不過尚未結束,這裏還有個比較重要的問題,多環境的時候application.properties/yml配置文件中指定了profile,就會加載application-{profile}.properties/yml是怎麼實現的呢?

 

加載多環境的application-{profile}配置文件

回到咱們以前的第四個load方法

private void load(
        PropertySourceLoader loader, 
        String location, 
        Profile profile, 
        DocumentFilter filter,
        DocumentConsumer consumer
        ) {
    try {
        // 獲取資源
        Resource resource = this.resourceLoader.getResource(location);
  
        // 加載爲Document對象
        List<Document> documents = loadDocuments(loader, name, resource);
   
        List<Document> loaded = new ArrayList<>();
        // 遍歷Document集合
        for (Document document : documents) {
            if (filter.match(document)) {
                // 添加profile
                addActiveProfiles(document.getActiveProfiles());
                addIncludedProfiles(document.getIncludeProfiles());
                loaded.add(document);
            }
        }
        Collections.reverse(loaded);
        if (!loaded.isEmpty()) {
            // 回調處理每一個document
            loaded.forEach((document) -> consumer.accept(profile, document));
        }
    } catch (Exception ex) {}
}

這裏,已經把默認的application.properties/yml給加載成爲了Document。而後在遍歷documents的時候,會把Document中的profiles作一次添加

 

咱們跟進addActiveProfiles看看

private Deque<Profile> profiles;

void addActiveProfiles(Set<Profile> profiles) {
    if (profiles.isEmpty()) {
        return;
    }
    // 省略
    // 添加到隊列
    this.profiles.addAll(profiles);
    // 省略
    this.activatedProfiles = true;
    // 移除掉default
    removeUnprocessedDefaultProfiles();
}

咱們看到,新的profiles首先會被添加到現有隊列中。最初的profiles=[null, "default"]。然後,咱們消費了null,profiles=["default"]。如今,咱們添加一個profile="test"。那麼,profiles=["default", "test"]。

再看最後一行removeUnprocessedDefaultProfiles,將會移除default。因此,最終profiles=["test"]。

 

再回到第一個load方法中

public void load() {
    this.profiles = new LinkedList<>();
    this.processedProfiles = new LinkedList<>();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap<>();
    // 初始化profiles
    initializeProfiles();
    while (!this.profiles.isEmpty()) {
        // 消費一個profile
        Profile profile = this.profiles.poll();
        // active的profile添加到Environment
        if (profile != null && !profile.isDefaultProfile()) {
            addProfileToEnvironment(profile.getName());
        }
        // 加載
        load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }
    // 重置Environment中的profiles
    resetEnvironmentProfiles(this.processedProfiles);
    // 加載
    load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    // 添加全部properties到Environment中
    addLoadedPropertySources();
}

這時候while循環裏面將會拿到profile="test",跟以前同樣一路下去,直到loadForFileExtension方法

 

跟進loadForFileExtension

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
        Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
    DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
    if (profile != null) {
        // 拼接如:application-test.properties/yml
        String profileSpecificFile = prefix + "-" + profile + fileExtension;
        // 加載文件
        load(loader, profileSpecificFile, profile, defaultFilter, consumer);
        load(loader, profileSpecificFile, profile, profileFilter, consumer);
        for (Profile processedProfile : this.processedProfiles) {
            if (processedProfile != null) {
                String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
                load(loader, previouslyLoaded, profile, profileFilter, consumer);
            }
        }
    }
    // 加載具體格式的文件
    load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

咱們看到,當有profile的時候文件名就再也不是application.properties/yml了。它會把profile給拼接上去,因此就變成了application-test.properties/yml,並加載文件。後續也同樣得最終添加到Environment當中。

 

總結

application配置文件的加載過程邏輯並不複雜,只是具體細節比較多,因此代碼中包含了很多附加的邏輯。那麼拋開細節,咱們能夠看到其實就是到相應的目錄下搜索相應的文件是否存在,加載到內存之後再添加到Environment當中。

至於具體的細節如:加載文件的時候編碼相關、多個文件相同配置是否覆蓋、加載器如何解析各類配置文件的內容有時間也能夠仔細閱讀。

相關文章
相關標籤/搜索