SpringBoot Profile使用詳解及配置源碼解析

在實踐的過程當中咱們常常會遇到不一樣的環境須要不一樣配置文件的狀況,若是每換一個環境從新修改配置文件或從新打包一次會比較麻煩,Spring Boot爲此提供了Profile配置來解決此問題。java

Profile的做用

Profile對應中文並無合適的翻譯,它的主要做用就是讓Spring Boot能夠根據不一樣環境提供不一樣的配置功能支持。spring

咱們常常遇到這樣的場景:有開發、測試、生產等環境,不一樣的環境又有不一樣的配置。若是每一個環境在部署時都須要修改配置文件將會很是麻煩,而經過Profile則能夠輕鬆解決改問題。springboot

Profile的基本使用

好比上述環境,咱們能夠在Spring Boot中建立4個文件:微信

  • applcation.properties:公共配置
  • application-dev.properties:開發環境配置
  • application-test.properties:測試環境配置
  • application-prod.properties:生產環境配置

在applcation.properties中配置公共配置,而後經過以下配置激活指定環境的配置:app

spring.profiles.active = prod

其中「prod」對照文件名中application-prod.properties。Spring Boot在處理時會獲取配置文件applcation.properties,而後經過指定的profile的值「prod」進行拼接,得到application-prod.properties文件的名稱和路徑。dom

舉例說明,好比在開發環境使用服務的端口爲8080,而在生產環境中須要使用18080端口。那麼,在application-prod.properties中配置以下:ide

server.port=8080

而在application-prod.properties中配置爲:post

server.port=18080

在使用不一樣環境時,能夠經過在applcation.properties中配置spring.profiles.active屬性值來進行指定(如上面示例),也能夠經過啓動命令參數進行指定:測試

java -jar springboot.jar --spring.profiles.active=prod

這樣打包後的程序只需經過命令行參數就可使用不一樣環境的配置文件。this

基於yml文件類型

若是配置文件是基於yml文件類型,還能夠將全部的配置放在同一個配置文件中:

spring:
  profiles: 
    active: prod

server: 
  port: 18080
  
---
spring: 
  profiles: dev  
  
server: 
  port: 8080  
  
---
spring: 
  profiles: test  
  
server: 
  port: 8081

上述配置以「---」進行分割,其中第一個位置的爲默認的profile,好比上述配置啓動時,默認使用18080端口。若是想使用指定的端口一樣能夠採用上述命令啓動時指定。

源碼解析

下面帶你們簡單看一下在Spring Boot中針對Profile的基本處理流程(不會過分細化具體操做)。在Spring Boot啓動的過程當中執行其run方法時,會執行以下一段代碼:

public ConfigurableApplicationContext run(String... args) {
    // ...
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // ...
    } 
    // ...
}

其中prepareEnvironment方法的相關代碼以下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // ...
    listeners.environmentPrepared(environment);
    // ...
}

在prepareEnvironment方法處理業務的過程當中會調用SpringApplicationRunListeners的environmentPrepared方法發佈事件。該方法會遍歷註冊在spring.factories中SpringApplicationRunListener實現類,而後調用其environmentPrepared方法。

其中spring.factories中SpringApplicationRunListener實現類註冊爲:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

SpringApplicationRunListeners方法中的調用代碼以下:

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

其中listeners即是註冊的類的集合,這裏默認只有EventPublishingRunListener。它的environmentPrepared方法實現爲:

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

其實就是發佈了一個監聽事件。該事件會被一樣註冊在spring.factories中的ConfigFileApplicationListener監聽到:

# Application Listeners
org.springframework.context.ApplicationListener=\
// ...
org.springframework.boot.context.config.ConfigFileApplicationListener
// ...

關於配置文件的核心處理便在ConfigFileApplicationListener中完成。在該類中咱們能夠看到不少熟悉的常量:

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {

    private static final String DEFAULT_PROPERTIES = "defaultProperties";

    // Note the order is from least to most specific (last one wins)
    private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

    private static final String DEFAULT_NAMES = "application";
    // ...
}

好比Spring Boot默認尋找的配置文件的名稱、默認掃描的類路徑等。

不只如此,該類還實現了監聽器SmartApplicationListener接口和EnvironmentPostProcessor接口也就是擁有了監聽器和環境處理的功能。

其onApplicationEvent方法對接收到事件進行判斷,若是是ApplicationEnvironmentPreparedEvent事件則調用onApplicationEnvironmentPreparedEvent方法進行處理,代碼以下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

onApplicationEnvironmentPreparedEvent會獲取到對應的EnvironmentPostProcessor並調用其postProcessEnvironment方法進行處理。而loadPostProcessors方法獲取的EnvironmentPostProcessor正是在spring.factories中配置的當前類。

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());
    }
}

通過一系列的調用,最終調用到該類的以下方法:

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

其中Loader類爲ConfigFileApplicationListener內部類,提供了具體處理配置文件優先級、profile、加載解析等功能。

好比,在Loader類的load方法中便有以下一段代碼:

for (PropertySourceLoader loader : this.propertySourceLoaders) {
    if (canLoadFileExtension(loader, location)) {
        load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
        return;
    }
}

該代碼遍歷PropertySourceLoader列表,並進行對應配置文件的解析,而這裏的列表中的PropertySourceLoader一樣配置在spring.factories中:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

查看這兩PropertySourceLoader的源代碼,會發現SpringBoot默認支持的配置文件格式及解析方法。

public class PropertiesPropertySourceLoader implements PropertySourceLoader {

    private static final String XML_FILE_EXTENSION = ".xml";

    @Override
    public String[] getFileExtensions() {
        return new String[] { "properties", "xml" };
    }

    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        Map<String, ?> properties = loadProperties(resource);
        // ...
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Map<String, ?> loadProperties(Resource resource) throws IOException {
        String filename = resource.getFilename();
        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
            return (Map) PropertiesLoaderUtils.loadProperties(resource);
        }
        return new OriginTrackedPropertiesLoader(resource).load();
    }
}

好比PropertiesPropertySourceLoader中支持了xml和properties兩種格式的配置文件,並分別提供了PropertiesLoaderUtils和OriginTrackedPropertiesLoader兩個類進行相應的處理。

一樣的YamlPropertySourceLoader支持yml和yaml格式的配置文件,而且採用OriginTrackedYamlLoader類進行解析。

public class YamlPropertySourceLoader implements PropertySourceLoader {

    @Override
    public String[] getFileExtensions() {
        return new String[] { "yml", "yaml" };
    }

    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        // ...
        List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
        // ...
    }
}

固然,在ConfigFileApplicationListener類中還實現了上面提到的如何拼接默認配置文件和profile的實現,相關代碼以下:

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
        Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    // ...
    if (profile != null) {
        String profileSpecificFile = prefix + "-" + profile + fileExtension;
        load(loader, profileSpecificFile, profile, defaultFilter, consumer);
        load(loader, profileSpecificFile, profile, profileFilter, consumer);
        // Try profile specific sections in files we've already processed
        for (Profile processedProfile : this.processedProfiles) {
            if (processedProfile != null) {
                String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
                load(loader, previouslyLoaded, profile, profileFilter, consumer);
            }
        }
    }
    // ...
}

ConfigFileApplicationListener類中還實現了其餘更多的功能,你們感興趣的話能夠debug進行閱讀。

原文連接:《SpringBoot Profile使用詳解及配置源碼解析

Spring技術視頻

CSDN學院:《Spring Boot 視頻教程全家桶》


程序新視界:精彩和成長都不容錯過

程序新視界-微信公衆號

相關文章
相關標籤/搜索