Spring 框架提供了profile
的功能,可以自定义profile
加载不同环境的配置文件或者 Bean,Spring 使用了一个spring.profiles.active
环境变量实现了上述功能。
Example
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="PropertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:config-${spring.active.profile}.properties" />
</bean>
</beans>
这样我们可以实现不同环境可以配置不同的变量;
例如:域名 cn | net
Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<beans profile="dev">
<bean id="PropertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:config-dev.properties" />
</bean>
</beans>
<beans profile="prod">
<bean id="PropertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:config-prod.properties" />
</bean>
</beans>
</beans>
和上面的例子是等同的
如果你还没看明白。。。。。。
背景
上面介绍过了,下面我要将的就是和上面第一个例子有关。
- 框架
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