前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot Profile使用详解及配置源码解析

SpringBoot Profile使用详解及配置源码解析

作者头像
程序新视界
发布2019-12-26 16:03:21
1.4K0
发布2019-12-26 16:03:21
举报
文章被收录于专栏:丑胖侠丑胖侠
在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,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中配置公共配置,然后通过如下配置激活指定环境的配置:

代码语言:javascript
复制
spring.profiles.active = prod

其中“prod”对照文件名中application-prod.properties。Spring Boot在处理时会获取配置文件applcation.properties,然后通过指定的profile的值“prod”进行拼接,获得application-prod.properties文件的名称和路径。

举例说明,比如在开发环境使用服务的端口为8080,而在生产环境中需要使用18080端口。那么,在application-prod.properties中配置如下:

代码语言:javascript
复制
server.port=8080

而在application-prod.properties中配置为:

代码语言:javascript
复制
server.port=18080

在使用不同环境时,可以通过在applcation.properties中配置spring.profiles.active属性值来进行指定(如上面示例),也可以通过启动命令参数进行指定:

代码语言:javascript
复制
java -jar springboot.jar --spring.profiles.active=prod

这样打包后的程序只需通过命令行参数就可以使用不同环境的配置文件。

基于yml文件类型

如果配置文件是基于yml文件类型,还可以将所有的配置放在同一个配置文件中:

代码语言:javascript
复制
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方法时,会执行如下一段代码:

代码语言:javascript
复制
public ConfigurableApplicationContext run(String... args) {
	// ...
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		// ...
	} 
	// ...
}

其中prepareEnvironment方法的相关代码如下:

代码语言:javascript
复制
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// ...
	listeners.environmentPrepared(environment);
	// ...
}

在prepareEnvironment方法处理业务的过程中会调用SpringApplicationRunListeners的environmentPrepared方法发布事件。该方法会遍历注册在spring.factories中SpringApplicationRunListener实现类,然后调用其environmentPrepared方法。

其中spring.factories中SpringApplicationRunListener实现类注册为:

代码语言:javascript
复制
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

SpringApplicationRunListeners方法中的调用代码如下:

代码语言:javascript
复制
void environmentPrepared(ConfigurableEnvironment environment) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.environmentPrepared(environment);
	}
}

其中listeners便是注册的类的集合,这里默认只有EventPublishingRunListener。它的environmentPrepared方法实现为:

代码语言:javascript
复制
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
	this.initialMulticaster
			.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

其实就是发布了一个监听事件。该事件会被同样注册在spring.factories中的ConfigFileApplicationListener监听到:

代码语言:javascript
复制
# Application Listeners
org.springframework.context.ApplicationListener=\
// ...
org.springframework.boot.context.config.ConfigFileApplicationListener
// ...

关于配置文件的核心处理便在ConfigFileApplicationListener中完成。在该类中我们可以看到很多熟悉的常量:

代码语言:javascript
复制
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方法进行处理,代码如下:

代码语言:javascript
复制
@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中配置的当前类。

代码语言:javascript
复制
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());
	}
}

经过一系列的调用,最终调用到该类的如下方法:

代码语言:javascript
复制
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	RandomValuePropertySource.addToEnvironment(environment);
	new Loader(environment, resourceLoader).load();
}

其中Loader类为ConfigFileApplicationListener内部类,提供了具体处理配置文件优先级、profile、加载解析等功能。

比如,在Loader类的load方法中便有如下一段代码:

代码语言:javascript
复制
for (PropertySourceLoader loader : this.propertySourceLoaders) {
	if (canLoadFileExtension(loader, location)) {
		load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
		return;
	}
}

该代码遍历PropertySourceLoader列表,并进行对应配置文件的解析,而这里的列表中的PropertySourceLoader同样配置在spring.factories中:

代码语言:javascript
复制
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

查看这两PropertySourceLoader的源代码,会发现SpringBoot默认支持的配置文件格式及解析方法。

代码语言:javascript
复制
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类进行解析。

代码语言:javascript
复制
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的实现,相关代码如下:

代码语言:javascript
复制
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使用详解及配置源码解析

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Profile的作用
  • Profile的基本使用
  • 基于yml文件类型
  • 源码解析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档