spring boot 2源碼系列(五)- 外部化配置

spring 外部化配置官方文檔 https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

Spring Boot允許外部化你的配置,這樣你就可以在不同的環境中使用相同的應用程序代碼,你可以使用properties文件、YAML文件、環境變量和命令行參數來外部化配置,屬性值可以通過使用@Value註解直接注入到你的bean中,通過Spring的Environment抽象訪問,或者通過@ConfigurationProperties綁定到結構化對象。
Spring Boot使用一種非常特殊的PropertySource命令,該命令旨在允許對值進行合理的覆蓋,屬性按以下順序考慮:

1、Devtools全局設置屬性在你的主目錄(~/.spring-boot-devtools.properties當devtools處於激活狀態時)。
2、測試中的@TestPropertySource註解
3、測試中的@SpringBootTestproperties註解屬性
4、命令行參數
5、來自SPRING_APPLICATION_JSON(嵌入在環境變量或系統屬性中的內聯JSON)的屬性
6、ServletConfig初始化參數
7、ServletContext初始化參數
8、java:comp/env中的JNDI屬性
9、Java系統屬性(System.getProperties())
10、操作系統環境變量
11、一個只有random.*屬性的RandomValuePropertySource
12、在你的jar包之外的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)
13、打包在jar中的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)
14、在你的jar包之外的應用程序屬性(application.properties和YAML 變體)
15、打包在jar中的應用程序屬性(application.properties和YAML 變體)
16、@PropertySource註解在你的@Configuration類上,對yaml文件無效
17、默認屬性(通過設置SpringApplication.setDefaultProperties指定)

下面實踐這些參數配置

新建一個MyApplicationRunner類輸出test.property屬性

@Component
public class MyApplicationRunner implements ApplicationRunner {

    @Value("${test.property}")
    String testProperty;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("###輸出test.property:"+testProperty);
    }
}

17、默認屬性(通過設置SpringApplication.setDefaultProperties指定)。

在啓動類中添加設置默認屬性。

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(SpringBootDemoApplication.class);
        Properties properties = new Properties();
        properties.setProperty("test.property", "17、默認屬性(通過設置SpringApplication.setDefaultProperties指定)");
        app.setDefaultProperties(properties);
        app.run(args);
    }

啓動工程,控制檯輸出     ###輸出test.property:17、默認屬性(通過設置SpringApplication.setDefaultProperties指定)

16、@PropertySource註解在你的@Configuration類上,對yaml文件無效。

啓動類包含了@Configuration註解,可以在啓動類上使用@PropertySource。

1、新建application-property-source.properties文件,配置test.property屬性

test.property=16、@PropertySource註解在你的@Configuration類上,對yaml文件無效

2、啓動類上配置  @PropertySource("classpath:application-property-source.properties")

@PropertySource("classpath:application-property-source.properties")
@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
         SpringApplication app = new SpringApplication(SpringBootDemoApplication.class);
        Properties properties = new Properties();
        properties.setProperty("test.property", "17、默認屬性(通過設置SpringApplication.setDefaultProperties指定)");
        app.setDefaultProperties(properties);
        app.run(args);

    }
}

啓動工程,控制檯輸出 ###輸出test.property:16、@PropertySource註解在你的@Configuration類上,對yaml文件無效

15、打包在jar中的應用程序屬性(application.properties和YAML 變體)

在application.properties文件配置:test.property=15、打包在jar中的應用程序屬性(application.properties和YAML 變體)

啓動工程,控制檯輸出   ###輸出test.property:15、打包在jar中的應用程序屬性(application.properties和YAML 變體)

14、在你的jar包之外的應用程序屬性(application.properties和YAML 變體)

1、新建目錄 C:\Users\Administrator\Desktop\my-location,即在桌面新建目錄my-location,my-location目錄添加一個application.properties。填寫配置

test.property=14、在你的jar包之外的應用程序屬性(application.properties和YAML 變體)

2、添加程序參數 --spring.config.additional-location=C:\Users\Administrator\Desktop\my-location\

使用spring.config.additional-location配置,除了默認位置外,還搜索額外的位置。

啓動工程,控制檯輸出   ###輸出test.property:14、在你的jar包之外的應用程序屬性(application.properties和YAML 變體)

13、打包在jar中的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)

1、新建 application-dev.properties 文件,配置 test.property=13、打包在jar中的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)

2、添加程序參數 --spring.profiles.active=dev 

啓動工程,控制檯輸出  ###輸出test.property:13、打包在jar中的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)

12、在你的jar包之外的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)

在C:\Users\Administrator\Desktop\my-location 中新增一個文件 application-dev.properties,添加配置 test.property=12、在你的jar包之外的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)

啓動工程,控制檯輸出  ###輸出test.property:12、在你的jar包之外的 特殊配置文件的 應用程序屬性(application-{profile}.properties和YAML 變體)

11、一個只有random.*屬性的RandomValuePropertySource

用於生成隨機數,在application.properties文件中配置:test.property=${random.int[20,30]}  。結果不會覆蓋12的配置。

10、操作系統環境變量

使用idea配置操作系統環境變量,Environment variables 中添加 test.property=10、操作系統環境變量

啓動工程,控制檯輸出  ###輸出test.property:10、操作系統環境變量

9、Java系統屬性(System.getProperties())

啓動類中設置java系統屬性 System.setProperty("test.property", "9、Java系統屬性(System.getProperties())");

@PropertySource("classpath:application-property-source.properties")
@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {

        System.setProperty("test.property", "9、Java系統屬性(System.getProperties())");

        SpringApplication app = new SpringApplication(SpringBootDemoApplication.class);
        Properties properties = new Properties();
        properties.setProperty("test.property", "17、默認屬性(通過設置SpringApplication.setDefaultProperties指定)");
        app.setDefaultProperties(properties);
        app.run(args);
    }
}

啓動工程,控制檯輸出  ###輸出test.property:9、Java系統屬性(System.getProperties())

第8、7、6不講了,我沒去實踐。

5、來自SPRING_APPLICATION_JSON(嵌入在環境變量或系統屬性中的內聯JSON)的屬性

Environment variables 中添加 SPRING_APPLICATION_JSON={"test.property":"5、來自SPRING_APPLICATION_JSON(嵌入在環境變量或系統屬性中的內聯JSON)的屬性"}

啓動工程,控制檯輸出  ###輸出test.property:###輸出test.property:5、來自SPRING_APPLICATION_JSON(嵌入在環境變量或系統屬性中的內聯JSON)的屬性

4、命令行參數

idea的Program Arguments 添加 --test.property=4、命令行參數

啓動工程,控制檯輸出  ###輸出test.property:4、命令行參數

3、測試中的@SpringBootTestproperties註解屬性

在測試類上添加 @SpringBootTest(properties = {"test.property=3、測試中的@SpringBootTestproperties註解屬性"})

@SpringBootTest(properties = {"test.property=3、測試中的@SpringBootTestproperties註解屬性"})
class SpringBootDemoApplicationTests {
    @Test
    void contextLoads() {
    }
}

運行測試類

啓動工程,控制檯輸出 ###輸出test.property:3、測試中的@SpringBootTestproperties註解屬性

2、測試中的@TestPropertySource註解

1、新建application-test-property-source.properties,添加配置

test.property=2、測試中的@TestPropertySource註解

2、測試類加上@SpringBootTest、@TestPropertySource({"classpath:application-test-property-source.properties"}) 註解

@SpringBootTest
@TestPropertySource({"classpath:application-test-property-source.properties"})
class SpringBootDemoApplicationTests {
    @Test
    void contextLoads() {
    }
}

啓動工程,控制檯輸出  ###輸出test.property:2、測試中的@TestPropertySource註解

1、Devtools全局設置屬性在你的主目錄(~/.spring-boot-devtools.properties當devtools處於激活狀態時)。

這個我整不出來。

外部化配置源碼

1、debug到SpringApplication#run(java.lang.String...)方法中,有下面這句代碼

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);   這是配置環境變量的方法。

1.1、prepareEnvironment(listeners, applicationArguments) 解析。

// 源碼位置 org.springframework.boot.SpringApplication.prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    /**
     * 創建Environment對象,本教程使用的是servlet環境,創建的是StandardServletEnvironment對象
     */
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    /**
     * 將命令行參數屬性添加到StandardServletEnvironment對象中
     */
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    /**
     * 運行Environment準備完畢事件
     */
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

1.1.1、ConfigurableEnvironment environment = getOrCreateEnvironment(); 解析

environment 對象如下圖所示:

propertySources和propertyResolver屬性在AbstractEnvironment類中定義如下:

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

// propertyResolver也存儲了propertySources的引用
private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);

propertySources的propertySourceList是一個CopyOnwriteArrayList寫時複製列表。

在MutablePropertySources中,propertySourceList定義如下:

List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

PropertySource是對鍵值對的抽象, 他的source屬性用於存儲鍵值對屬性。

propertyResolver用於解析propertySources。

1.1.2、執行完   configureEnvironment(environment, applicationArguments.getSourceArgs());   這行代碼後,再看下 environment 會發生哪些變化。

最明顯的變化是propertySourceList多了兩個元素,其中SimpleCommandLinePropertySource的source承載了命令行參數屬性。而MapPropertySource的source屬性承載了properties.setProperty(key, value)設置的默認屬性。

使用Environment的getProperty(String key)方法時,通過propertyResolver去遍歷propertySourceList,找到key對應的值就把值返回。由於SimpleCommandLinePropertySource排在MapPropertySource的前面,getProperty("test.property")就會返回命令行中配置的值“4、命令行參數”。獲取屬性值的源碼分析後面會講。

1.1.3、listeners.environmentPrepared(environment); 解析

listeners是SpringApplicationRunListeners對象,是SpringApplicationRunListener的合集。環境變量的處理會使用到SpringApplication運行時監聽器,關於監聽器的內容可以查看 spring boot 2源碼系列(二)- 監聽器ApplicationListener 這篇博客。

EventPublishingRunListener是SpringApplicationRunListener的默認實現,會廣播ApplicationEnvironmentPreparedEvent事件。源碼位置 EventPublishingRunListener#environmentPrepared(ConfigurableEnvironment environment)

1.1.3.1、ApplicationEnvironmentPreparedEvent被 ConfigFileApplicationListener監聽到

// 源碼位置 org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    /**
     * getApplicationListeners(event, type)
     * 獲取監聽了 ApplicationEnvironmentPreparedEvent 事件的監聽器,調用監聽器的 onApplicationEvent(E event) 方法。
     * getApplicationListeners(event, type)獲取到的監聽器中包含ConfigFileApplicationListener實例
     * ConfigFileApplicationListener將加載配置文件
     */
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

1.1.3.1.1、ConfigFileApplicationListener監聽到ApplicationEnvironmentPreparedEvent事件後,使用環境變量後置處理器將配置的屬性綁定到Environment中。

    // 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        /**
         * 使用環境變量後置處理器將屬性添加到Environment的propertySources中
         * postProcessors包含很多個環境變量後置處理器,例如:
         * SystemEnvironmentPropertySourceEnvironmentPostProcessor 處理系統屬性
         * SpringApplicationJsonEnvironmentPostProcessor 處理SPRING_APPLICATION_JSON屬性
         * ConfigFileApplicationListener 處理properties、yml配置文件
         */
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }
    }

當for (EnvironmentPostProcessor postProcessor : postProcessors) 循環完畢,Environment對象就變成了下面這樣

配置文件application-{profile}.properties被存儲到了propertySourceList的OriginTrackedMapPropertySource對象中,一個配置文件對應一個OriginTrackedMapPropertySource對象。

下面來分析application-{profile}.properties的加載過程

當 for (EnvironmentPostProcessor postProcessor : postProcessors) 遍歷到ConfigFileApplicationListener時,debug進入方法內,最終進入addPropertySources方法

// 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.addPropertySources
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    // 使用Loader加載屬性源並配置active文件
    new Loader(environment, resourceLoader).load();
}

Loader構造函數主要做一些屬性賦值操作。

1、下面看下load()方法,這個方法就很重要了。

// 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.Loader.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<>();

                // 初始化profile
                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));

                 // 配置文件鍵值對綁定到environment對象
                addLoadedPropertySources();
                applyActiveProfiles(defaultProperties);
            });
}

1.1、initializeProfiles方法解析。

// 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#initializeProfiles()
private void initializeProfiles() {
    /**
     * 給profiles添加一個null元素比較有意思。
     * 可以把application.properties、application.yml的profile認爲是null。
     * 先添加null元素,即先處理默認配置文件application.properties、application.yml
     */
    this.profiles.add(null);

    /**
     * 此時還沒加載配置文件,只能讀取命令行屬性、SPRING_APPLICATION_JSON、系統環境變量等方式配置的spring.profiles.active、spring.profiles.include
     */
    Set<ConfigFileApplicationListener.Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
    Set<ConfigFileApplicationListener.Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);

    List<ConfigFileApplicationListener.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);

    /**
     * 如果沒有配置spring.profiles.active、spring.profiles.include 就加一個default的profile
     */
    if (this.profiles.size() == 1) { // only has null profile
        for (String defaultProfileName : this.environment.getDefaultProfiles()) {
            ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(defaultProfileName, true);
            this.profiles.add(defaultProfile);
        }
    }
}

initializeProfiles()執行完後,this.profiles=[null, 命令行等方式配置的spring.profiles.active和spring.profiles.include/或者default]。
在本教程中this.profiles=[null, "dev"]。然後便是循環this.profiles。

1.1.1、while循環中的 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));解析

// 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load()
private void load(String location, String name, ConfigFileApplicationListener.Profile
profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory,
        ConfigFileApplicationListener.DocumentConsumer consumer) {

    //省略部分代碼

    /**
     * this.propertySourceLoaders有2個loader:
     *     PropertiesPropertySourceLoader("properties", "xml")、YamlPropertySourceLoader("yml", "yaml")
     */
    for (PropertySourceLoader loader : this.propertySourceLoaders) {
        for (String fileExtension : loader.getFileExtensions()) {
            if (processed.add(fileExtension)) {

                // 使用loader加載配置文件
                loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
                        consumer);
            }
        }
    }
}

由這兩個同名load方法可以看出

(1)、先通過getSearchLocations()獲取配置文件的存放目錄(是個集合),然後遍歷目錄集合。

(2)、在遍歷目錄的過程中,遍歷加載器集合this.propertySourceLoaders,使用加載器加載相應的文件類型,可加載"properties"、"xml"、"yml"、"yaml"這4種文件類型。

1.1.1.1、加載器加載配置文件的過程

// 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load()
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
        DocumentConsumer consumer) {

    // 僅列出主要代碼

    // 定位資源
    Resource resource = this.resourceLoader.getResource(location);

    // 將配置文件鍵值對加載到Document對象中
    List<Document> documents = loadDocuments(loader, name, resource);

    List<Document> loaded = new ArrayList<>();
    for (Document document : documents) {
        if (filter.match(document)) {
            /**
             * 如果配置文件中配置了spring.profiles.active、spring.profiles.include,
             * 也添加到ConfigFileApplicationListener的profiles屬性中
             */
            addActiveProfiles(document.getActiveProfiles());
            addIncludedProfiles(document.getIncludeProfiles());
            // document添加到loaded
            loaded.add(document);
        }
    }

    /**
     * 遍歷loaded
     * consumer.accept(profile, document)是將document添加到MutablePropertySources的propertySourceList中
     */
    loaded.forEach((document) -> consumer.accept(profile, document));
}

List<Document> documents = loadDocuments(loader, name, resource); 這方法的主要代碼如下:

// 源碼位置 org.springframework.boot.env.PropertiesPropertySourceLoader.load()
// PropertiesPropertySourceLoader可以加載xml和properties文件
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
    // PropertiesPropertySourceLoader使用一個map存儲配置文件中的鍵值對
    Map<String, ?> properties = loadProperties(resource);
    if (properties.isEmpty()) {
        return Collections.emptyList();
    }
    return Collections
            .singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}

Map<String, ?> properties = loadProperties(resource);詳解

// 源碼位置 org.springframework.boot.env.OriginTrackedPropertiesLoader.load(boolean)
Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
    //僅列表部分代碼    
    // 使用CharacterReader讀取配置文件
    try (CharacterReader reader = new CharacterReader(this.resource)) {
        Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
        StringBuilder buffer = new StringBuilder();
        while (reader.read()) {
            // 添加鍵值對到result中
            put(result, key, value);
        }
        // 返回result
        return result;
    }
}

配置文件鍵值對存儲到在document的propertySource屬性中

loaded.forEach((document) -> consumer.accept(profile, document));  繼續debug這句代碼,來到了

// 源碼位置 org.springframework.core.env.MutablePropertySources.addLast()
public void addLast(PropertySource<?> propertySource) {
    removeIfPresent(propertySource);
    /**
     * this是loaded,先將配置文件鍵值對添加到loaded的propertySourceList中
     */
    this.propertySourceList.add(propertySource);
}

1.2、再回到 ConfigFileApplicationListener.Loader#load() 方法,load()方法中的 addLoadedPropertySources(); 便是將loaded的PropertySource添加給environment。

// 源碼位置 org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#addLoadedPropertySources
private void addLoadedPropertySources() {
    // 獲取environment的propertySources
    MutablePropertySources destination = this.environment.getPropertySources();
    // 獲取暫存在loaded中的鍵值對配置
    List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());

    // 遍歷loaded
    for (MutablePropertySources sources : loaded) {
        // 將loaded添加到destination中
        addLoadedPropertySource(destination, lastAdded, source);
    }
}

至此,配置文件的鍵值對如何添加到environment對象中就講完了。

獲取Environment屬性源碼分析

新建一個MyEnvironmentAware

@Component
public class MyEnvironmentAware implements EnvironmentAware {

    private static Environment env;

    public static String getProperty(String key){
        String pk = env.getProperty(key);
        System.out.println(pk);
        return pk;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }

}

然後在 MyApplicationRunner#run方法中加入一行代碼

MyEnvironmentAware.getProperty("test.property")

啓動工程,debug到Environment的getProperty(String key)方法內部。

1、最開始是調用propertyResolver的getProperty方法

// 源碼位置 org.springframework.core.env.AbstractEnvironment.getProperty(java.lang.String)
public String getProperty(String key) {
    // 調用propertyResolver的getProperty方法
    return this.propertyResolver.getProperty(key);
}

2、getProperty解析

 //源碼位置 org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(java.lang.String, java.lang.Class<T>, boolean)
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        // 僅展示部分代碼
        // 遍歷this.propertySources
        for (PropertySource<?> propertySource : this.propertySources) {
            // propertySource.getProperty(key);其實就是獲取獲取source中的鍵值對
            Object value = propertySource.getProperty(key);
            /**
             * 如果value帶有佔位符
             * 1、截取佔位符文本
             * 2、再次遍歷this.propertySources,獲取到value。
             * 從第2點可以看出,使用佔位符與外部化配置的優先級無關。
             * 比方說:命令行參數的值是佔位符,佔位符的作爲key可以配置到application.properties中.
             */
            if (value != null) {
                if (resolveNestedPlaceholders && value instanceof String) {
                    value = resolveNestedPlaceholders((String) value);
                }
            }
        }
    }
}

 Object value = propertySource.getProperty(key); 詳解

// 源碼位置 org.springframework.core.env.MapPropertySource#getProperty
public Object getProperty(String name) {
	// Object value = propertySource.getProperty(key); 就是取source中的值
	return this.source.get(name);
}

this.propertySources如下圖所示

命令行屬性的source如下圖所示

 

 

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