Spring 框架提供了profile的功能,可以自定义profile加载不同环境的配置文件或者 Bean,Spring 使用了一个spring.profiles.active环境变量实现了上述功能。

Example

配置文件

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

这样我们可以实现不同环境可以配置不同的变量;

例如:域名 cn | net

Bean

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

和上面的例子是等同的

如果你还没看明白。。。。。。

背景

上面介绍过了,下面我要将的就是和上面第一个例子有关。

框架

SpringBoot

版本

1.5.1.RELEASE

当然 SpringBoot 基于 Spring 的,那么它当然也支持spring.profiles.active,而且 SpringBoot 使用spring.profiles.active可以实现自动加载不同的 properties 文件。

Example

结构

其中 SpringBoot默认会加载application.properties,如果环境变量中有spring.active.profile=dev,那么 SpringBoot 就会加载application-dev.properties;那如果我们线上的环境变量不是spring.profiles.active=dev而且一个其他的,例如是-Denvironment=prod,我们的项目已经上线了,而且服务器还有很多台,如果直接在线上改,好像也不是很现实;那我们怎么兼容呢?

那有人要问了,为什么一定要兼容?我们在项目中使用environment环境变量不是一样可以区分线上线下吗?

是可以不兼容,但是我们知道 SpringBoot 加载 Bean 都是使用注解的方式,如果我们想使用 @Profile 加载不同环境的 Bean 呢?那该怎么办?我们是不是要使用兼容;当然兼容的方式有很多;我们今天介绍一个很简单的方式;

Step 1

我们知道 Spring 中把环境变量都加载到org.springframework.core.env.Environment的实现类中,项目使用占位符或者@Value都是使用org.springframework.core.env.Environment#resolvePlaceholders或者org.springframework.core.env.Environment#resolveRequiredPlaceholders来解析的。

测试例子来说明情况

public class EnvironmentTest {
@Test
public void propertyTest() {
/**
* 这里我只是模拟了 Spring 启动后加载 properties 的情况
*/
String key = "${environment}";
MutablePropertySources mutablePropertySources = new MutablePropertySources();
// properteis 文件
Properties properties = new Properties();
// 这里我们模拟了环境变量的参数
properties.put("environment", "dev");
// properties 文件中 spring.profiles.active=${environment}
properties.put("spring.profiles.active", key);
PropertySource propertySource = new PropertiesPropertySource("Properteis", properties);
mutablePropertySources.addFirst(propertySource);
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addFirst(propertySource);
// 获取 ${environment} 的值
String property = environment.getProperty(key);
System.out.println(String.format("Environment#getProperty(%s) = %s", key, property));
// 解析占位符 ${environment}
String placeholders = environment.resolvePlaceholders(key);
System.out.println(String.format("Environment#resolvePlaceholders(%s) = %s", key, placeholders));
}
}

打印结果

Environment#getProperty(${environment}) = null

Environment#resolvePlaceholders(${environment}) = dev

可以参考

居然 Spring 可以解析占位符那么 SpringBoot也是一样可以的,我们知道之前我们做 Spring 项目的时候,application.xml就是使用了占位符,那既然application.xml占位符都能解析,如果我们是否可以在application.properties中添加一个变量spring.profiles.active=${environment},这样 SpringBoot 在启动项目加载application.properties文件时,是否可以把${environment}解析成环境变量environment对应的值呢?

application.properties

spring.profiles.active=${environment}

答案是可以的

具体实现可以参考源码org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load()

Step 2

但是在具体做项目时,Step 1居然没有实现,我们通过Environment#getProperty('spring.profiles.active')返回的值居然是${environment},很纳闷;

那就源码org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load()看看 SpringBoot 如何实现;

前面如何加载 properties 我们就省略掉,直接看在加载 properties 内容是如何实现的,也就是spring.profiles.active;

private void handleProfileProperties(PropertySource> propertySource) {
SpringProfiles springProfiles = bindSpringProfiles(propertySource);
maybeActivateProfiles(springProfiles.getActiveProfiles());
addProfiles(springProfiles.getIncludeProfiles());
}
private SpringProfiles bindSpringProfiles(PropertySources propertySources) {
SpringProfiles springProfiles = new SpringProfiles();
RelaxedDataBinder dataBinder = new RelaxedDataBinder(springProfiles,
"spring.profiles");
dataBinder.bind(new PropertySourcesPropertyValues(propertySources));
return springProfiles;
}

handleProfileProperties

加载配置文件属性,并且把已经解析后的profile,放到一个profile集合中,下一次循环加载配置文件是,从profile拿到值,拼接成一个application-{profile}.properties的文件,并且加载这个文件。

bindSpringProfiles

从配置文件中加载spring.profiles.active属性

上面我介绍了 SpringBoot 占位符是使用org.springframework.core.env.Environment#resolvePlaceholders或者org.springframework.core.env.Environment#resolveRequiredPlaceholders这两个方法去解析的;我在调试时没有发现使用这个方法,这就说明 SpringBoot 1.5.1.RELEASE 版本不支持application.properties配置文件中占位符的解析;解决这个问题我们可以有不同方案;最好的方案就是升级 SpringBoot 版本,SpringBoot 从1.5.2.RELEASE 版本之后可以支持了这个功能。

Step 3

如果没看过上面我推荐的两篇文件,有些人该纳闷了,我配置文件都没加载,配置文件中也没有environment=xxx的属性呀,并且这个属性是我们加载在 Java 中的环境变量,为啥org.springframework.core.env.Environment可以解析到environment。

这是因为 Spring 会把 Java 系统属性System.getProperties()和环境变量System.getenv()加载到 Environment 中,这就是为啥,我们可以使用 Environment 获取 Java 中的属性和环境变量了。

测试例子说明

public class EnvironmentTest {
@Test
public void propertyTest() {
// Java 系统属性
String key = "java.home";
StandardEnvironment environment = new StandardEnvironment();
// 获取 ${environment} 的值
String property = environment.getProperty(key);
System.out.println(String.format("Environment#getProperty(%s) = %s", key, property));
// 解析占位符 ${environment}
String placeholders = environment.resolvePlaceholders("${java.home}");
System.out.println(String.format("Environment#resolvePlaceholders(${java.home}) = %s", placeholders));
}
}

打印结果

Environment#getProperty(java.home) = /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre

Environment#resolvePlaceholders(${java.home}) = /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre