Spring中的Profiles

摘自Environment注释:

A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the @Profile annotation for syntax details. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.

profiles类似Maven中的profiles,Spring允许配置多个profile,但只加载激活的profile,通过profiles配置,可以实现不同环境使用不同的配置(例如不同环境使用不同的数据库配置)

配置

XML方式

1
2
3
<beans profile="local">
<bean id="localBeanService" class="xyz.iyichen.misc.learning.spring.tp.LocalBeanService"/>
</beans>

注解方式

1
2
3
4
5
6
7
8
9
@Configuration
public class Application {

@Bean
@Profile("local")
public LocalBeanService localBeanService() {
return new LocalBeanService();
}
}

使用

通过spring.profiles.active

1
spring.profiles.active=local

例如在启动命令中添加-Dspring.profiles.active=local

通过方法设置

1
ConfigurableEnvironment.setActiveProfiles(String... profiles);

例如:

1
2
3
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"spring-context.xml"}, false);
ac.getEnvironment().setActiveProfiles("local");
ac.refresh();

实现

XML方式

当使用xml配置时,Spring是通过解析xml实现Bean的注册的。那么当解析到<beans profile="local">,根据profile是否可用决定是否注册Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// DefaultBeanDefinitionDocumentReader.java
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

if (this.delegate.isDefaultNamespace(root)) {
// 解析`beans`节点的`profile`属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 判断解析到的`profile`在`environment`是否可用
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}

preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

this.delegate = parent;
}

方法调用顺序如下(新标签打开图片):

注解实现

当使用注解时,在注册Bean时,判断是否包含@Profile,如果不包含,则直接注册;如果没有,则判断该profile是否可用,可用则注册,否则不做注册。判断逻辑在ProfileCondition.matches()中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ProfileCondition.java
class ProfileCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// 获取注解`porfile`属性信息
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
// 判断解析到的`profile`在`environment`是否可用
// 如果返回`true`,则注册该`bean`,否则不注册
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}

方法调用顺序如下(新标签打开图片):

番外

当初始化ClassPathXmlApplicationContext时,看下setConfigLocations()方法:

1
2
3
4
5
6
7
8
9
10
// ClassPathXmlApplicationContext.java
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {

super(parent);
// 调用父类`AbstractRefreshableConfigApplicationContext`,设置配置文件路径
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AbstractRefreshableConfigApplicationContext.java
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

protected String resolvePath(String path) {
// 对`Spring`配置文件路径做占位符替换
return getEnvironment().resolveRequiredPlaceholders(path);
}

如果再看getEnvironment()方法,会发现默认情况下使用的就是StandardEnvironment(初始化时加载系统配置和启动配置),而且可以看到,配置文件路径也支持占位符替换

因此,可以根据不同启动参数,加载不同的配置文件。例如:

1.创建两个Spring配置文件:spring-context-local.xmlspring-context-rc.xml

2.启动类

1
2
3
// 使用占位符方式
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"spring-context-${custom.profile}.xml"}, false);
ac.refresh();

3.启动命令

1
2
// 使用配置文件`spring-context-rc.xml`
-Dcustom.profile=rc

参考

beans-definition-profiles