在實踐的過程中我們經常會遇到不同的環境需要不同配置文件的情況,如果每換一個環境重新修改配置文件或重新打包一次會比較麻煩,Spring Boot為此提供了Profile配置來解決此問題。
Profile的作用
Profile對應中文並沒有合適的翻譯,它的主要作用就是讓Spring Boot可以根據不同環境提供不同的配置功能支持。
我們經常遇到這樣的場景:有開發、測試、生產等環境,不同的環境又有不同的配置。如果每個環境在部署時都需要修改配置文件將會非常麻煩,而通過Profile則可以輕松解決改問題。
Profile的基本使用
比如上述環境,我們可以在Spring Boot中創建4個文件:
- applcation.properties:公共配置
- application-dev.properties:開發環境配置
- application-test.properties:測試環境配置
- application-prod.properties:生產環境配置
在applcation.properties中配置公共配置,然后通過如下配置激活指定環境的配置:
spring.profiles.active = prod
其中“prod”對照文件名中application-prod.properties。Spring Boot在處理時會獲取配置文件applcation.properties,然后通過指定的profile的值“prod”進行拼接,獲得application-prod.properties文件的名稱和路徑。
舉例說明,比如在開發環境使用服務的端口為8080,而在生產環境中需要使用18080端口。那么,在application-prod.properties中配置如下:
server.port=8080
而在application-prod.properties中配置為:
server.port=18080
在使用不同環境時,可以通過在applcation.properties中配置spring.profiles.active屬性值來進行指定(如上面示例),也可以通過啟動命令參數進行指定:
java -jar springboot.jar --spring.profiles.active=prod
這樣打包后的程序只需通過命令行參數就可以使用不同環境的配置文件。
基於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 視頻教程全家桶》