從源碼分析springboot環境配置加載

從源碼分析springboot環境配置加載

一直沒有搞清楚springboot環境配置信息到底是怎麼加載的,是不是在啓動時指定–spring.profiles.active之後spring就去指定讀取這個文件了,因此這次從源碼角度研究一下它的加載過程。

首先從入口開始分析:

public static void main(String[] args) {
    //這裏的run是springboot啓動入口
    SpringApplication.run(StarServiceAgencyOrderCoreApplication.class, args);
}
public ConfigurableApplicationContext run(String... args) {
            //其他的先不管,springboot的環境信息封裝在Environment這個對象中,加載也是從這裏開始的,
    	   //而在Environment對象中有個MutablePropertySources,這裏是存儲具體的環境配置信息的地方
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
          
    }

接下來開始分析一下prepareEnvironment大致幹了些啥。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    	//創建environment對象
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    	//做了一些配置初始化,以及加載了一些默認信息,這裏可以看到在這個方法執行結束後environment中的propertySource中加載了一些信息,這裏其實是在environment初始化時就已經初始化好的,在構造方法中就可以看到了
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    	//從這裏開始加載了。
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

可以看到這一塊加載是通過listener去完成的。

public void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();

        while(var2.hasNext()) {
            //這裏通過的EventPublishingRunListener來處理
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            listener.environmentPrepared(environment);
        }

}

接下來在來看EventPublishingRunListener。

//這裏沒啥好看的,一層一層剝洋蔥吧。
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
//這裏也沒有看到重點,繼續剝
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }
}

一直到ConfigFileApplicationListener類中的onApplicationEvent方法。可以看到ConfigFileApplicationListener中的成員變量,有那麼一些熟悉的字眼,接下來還有幾層,從ConfigFileApplicationListener#onApplicationEvent() ->onApplicationEnvironmentPreparedEvent->postProcessEnvironment->addPropertySources->ConfigFileApplicationListener.Loader#load。一直到這裏才真正開始加載了。

public void load() {
    //...
    //這個方法初始化profiles的信息,
    this.initializeProfiles();
	//...
    //開始加載配置文件信息
        this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
}

這個方法還是看一下,在這裏可以看到在Profiles中首先添加了一個null,另外添加了指定的active配置信息,如果active沒有找到的情況下,默認添加了一個default。這也就是springboot會默認加載application-default.xx文件的原因。至於爲什麼添加一個null,在後面代碼處就可以看明白了。

private void initializeProfiles() {
    this.profiles.add((Object)null);
    Set<ConfigFileApplicationListener.Profile> activatedViaProperty = this.getProfilesActivatedViaProperty();
    this.profiles.addAll(this.getOtherActiveProfiles(activatedViaProperty));
    this.addActiveProfiles(activatedViaProperty);
    if (this.profiles.size() == 1) {
        String[] var2 = this.environment.getDefaultProfiles();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String defaultProfileName = var2[var4];
            ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }

}

接下來看一下load方法。

private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
    this.getSearchLocations().forEach((location) -> {
        boolean isFolder = location.endsWith("/");
        //這個name是配置文件前綴名。
        Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
        names.forEach((name) -> {
            //加載的方法
            this.load(location, name, profile, filterFactory, consumer);
        });
    });
}
//這裏還是在準備讀取過程中
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            //...
                        this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
            	//...
                //在這裏可以看到spring-default.xx的文件是在這裏拼接上去的
                String profileSpecificFile = prefix + "-" + profile + fileExtension;
			//...
            this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
 }

//完成配置文件加載的方法
private void load(PropertySourceLoader loader, String location, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilter filter, ConfigFileApplicationListener.DocumentConsumer consumer) {
                //通過resourceLoader去尋找文件
                Resource resource = this.resourceLoader.getResource(location);
                StringBuilder descriptionxx;
                		//...
                        String name = "applicationConfig: [" + location + "]";
                		//最終在loadDocument中完成加載
                        List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
                       //... 
}

private List<ConfigFileApplicationListener.Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
            //..             
    		//加載完成,是properties文件就通過PropertiesPropertySourceLoader加載,是yaml文件就通過YamlPropertySourceLoader加載。
                List<PropertySource<?>> loaded = loader.load(name, resource);
              //..
        }

附上分析的時序圖:在這裏插入圖片描述到這初始化的配置文件加載就分析的差不多了。可以得出的結論是spring在初始化時application.xx文件一定會加載,另外則是看你配置的active指定環境文件。那麼最後,假如兩邊都定義了一個變量,spring是怎麼去取值呢,哪個值優先呢?

那麼回到SpringApplication中,refreshContext看spring容器的初始化方法,這裏是入口,具體的步驟很複雜,可以自己跟一下或者單獨去看spring的初始化過程,這裏就省略了。那麼跳到PropertySourcesPropertyResolver#getProperty,這裏是spring中對初始化對象賦值的其中一個方法。

//這裏的key就是你要賦值的對象key,例如你定義的成員變量
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        //這個propertySources就是剛纔加載配置文件後拿到的MutablePropertySources,而這裏賦值是通過循環來查找PropertySources中對應存在的key值,找到後就return了。
        Iterator var4 = this.propertySources.iterator();

        while(var4.hasNext()) {
            PropertySource<?> propertySource = (PropertySource)var4.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
            }
		   //取值過程
            Object value = propertySource.getProperty(key);
            if (value != null) {
                if (resolveNestedPlaceholders && value instanceof String) {
                    value = this.resolveNestedPlaceholders((String)value);
                }

                this.logKeyFound(key, propertySource, value);
                //取到值後就return了
                return this.convertValueIfNecessary(value, targetValueType);
            }
        }
    }

    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Could not find key '" + key + "' in any property source");
    }

    return null;
}

從上面的代碼可以看出來,假如兩邊都定義了一個變量,spring是怎麼去取值優先順序跟MutablePropertySources中的順序有關,具體的順序spring官方給出了定義。參考:https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config 中的Externalized Configuration項。如下:

Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:

  1. Devtools global settings properties in the $HOME/.config/spring-boot folder when devtools is active.
  2. @TestPropertySource annotations on your tests.
  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified by setting SpringApplication.setDefaultProperties).

自此springboot的配置項加載分析完成。

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