在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,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 视频教程全家桶》

程序新视界:精彩和成长都不容错过
![程序新视界-微信公众号](https://img2018.cnblogs.com/blog/1742867/201910/1742867-20191013111755842-2090947098.png)

SpringBoot Profile使用详解及配置源码解析的更多相关文章

  1. 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING

    <Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th  2014 Email:skyseraph00@163.com 更多精彩请直接 ...

  2. Android中Canvas绘图基础详解(附源码下载) (转)

    Android中Canvas绘图基础详解(附源码下载) 原文链接  http://blog.csdn.net/iispring/article/details/49770651   AndroidCa ...

  3. Android事件传递机制详解及最新源码分析——ViewGroup篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...

  4. 【详解】ThreadPoolExecutor源码阅读(三)

    系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) 线程数量的 ...

  5. 【详解】ThreadPoolExecutor源码阅读(二)

    系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) AQS在W ...

  6. 【详解】ThreadPoolExecutor源码阅读(一)

    系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) 工作原理简 ...

  7. Android 网络框架之Retrofit2使用详解及从源码中解析原理

    就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题: 1 . 什么是Retrofit? Retrofit是针对于Android/Java的.基于okHttp的.一种轻量级且安全 ...

  8. tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解

    TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...

  9. Android事件传递机制详解及最新源码分析——Activity篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...

随机推荐

  1. PHP流程控制之if语句多种嵌套

    王思总同学我们在最开始的故事中讲到了他有两个秘书:一个生活秘书.一个工作秘书. 王思总同学在出行和项目中也是极度有计划性.他给自己的生活秘书和工作秘书分别指派了出差的行程:大理石平台支架 生活上: 先 ...

  2. Elasticsearch原理讲透

    小史是一个非科班的程序员,虽然学的是电子专业,但是通过自己的努力成功通过了面试,现在要开始迎接新生活了. 随着央视诗词大会的热播,小史开始对诗词感兴趣,最喜欢的就是飞花令的环节. 但是由于小史很久没有 ...

  3. CF369E Valera and Queries kdtree

    给你一堆线段,求:一个区间内包含的本质不同线段种类数(只要线段有一部分在区间中就算是包含) 考虑容斥:总线段数-被那些没有询问的区间完全覆盖的数量. 用离线+树状数组数点或者 KDtree 数点即可. ...

  4. 第12章、乐活人生的ABCDE

    目录 第12章.乐活人生的ABCDE 什么时候该乐观 让自己乐观的ABC 确认ABC 你的ABC记录 反驳和转移注意 转移注意 反驳 保持距离 学习与自己争辩 证据 其他可能性 暗示 用处 你的反驳记 ...

  5. CUDA编程前言

    GPU架构 GPU特别适用于 密集计算,高度可并行计算,图形学 晶体管主要被用于 执行计算,而不是缓存数据,控制指令流 GPU计算的历史 2001/2002 -- 研究人员把GPU当做数据并行协处理器 ...

  6. OpenJudge 1.5.27:级数求和

    描述 已知:Sn= 1+1/2+1/3+…+1/n.显然对于任意一个整数K,当n足够大的时候,Sn大于K. 现给出一个整数K(1<=k<=15),要求计算出一个最小的n:使得Sn>K ...

  7. EXTJS框架-入门实例

    extjs框架是一个JavaScript框架,可以渲染出丰富的控件 实例: 代码: <html> <head> <title>test</title> ...

  8. vue 传参props里面为什么要带type,还有default?

    这个是子组件啦 ,写type的意思是swiperDate传过来的数据类型是数组,default就是表示不传默认返回的[ ],空数组. 这种就是表示传的数据类型是number,不传默认是0.

  9. mac下生成keystore

    keytool -genkey -v -keystore sign.keystore -alias cxa -keyalg RSA -validity 20000 -keystore /Users/c ...

  10. Vue学习手记01-安装和项目创建

    1.安装Vue  注:node版本必须大于等于8.9  vue-cli3.x:npm install -g @vue/cli  vue-cli2.x:npm install -g @vue/cli-i ...