开篇
对于XML配置文件来说,一般情况下根元素就是beans。XML配置文件被加载为document之后,会从根元素开始读取bean配置为BeanDefinition对象。但是,<beans>的解析开始前,其实还有profile属性的判断,只有profile匹配才会继续解析。
可以在整个配置文件的根元素配置profile属性,比如:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="...">
</beans>
也可以在嵌套的beans元素上定义,比如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
Profile如何起作用
不管是根元素还是嵌套的beans元素,解析逻辑都是DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
从代码可以看到,beans元素的profile属性可以配置多个值。 如果当前环境不接受specifiedProfiles,那么beans元素不会解析。 如果没有配置profile属性,那么会直接解析,不会做拦截。
Spring默认情况下,Environment的具体实现类是StandardEnvironment,那么来看下acceptsProfiles的具体实现:
@Deprecated
public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
for (String profile : profiles) {
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
if (!isProfileActive(profile.substring(1))) {
return true;
}
}
else if (isProfileActive(profile)) {
return true;
}
}
return false;
}
protected boolean isProfileActive(String profile) {
validateProfile(profile);
Set<String> currentActiveProfiles = doGetActiveProfiles();
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}
首先,比较明显的是方法已经被标记为已过期,目前它也只有在xml文件解析的地方才有用到。从使用处的注解也可以了解到XML配置有些不足之处,而且现在毋庸置疑已经是注解的天下了,但是这不影响我们先来了解下Spring的机制。
// We cannot use Profiles.of(…) since profile expressions are not supported // in XML config. See SPR-12458 for details.
代码的执行逻辑还是比较清楚的,简单来说就是,beans元素的profile属性中可以配置多个,只要这些profiles有任何一个满足条件,就认为beans元素匹配。而且,支持配置非("!")操作。
两种profile生效的属性配置
从上面isProfileActive方法可以看出,如果存在ActiveProfiles 那么会使用currentActiveProfiles 来匹配beans元素的profile,如果没有ActiveProfiles 那么将使用DefaultProfiles来匹配。
进入doGetActiveProfiles可以看到,读取的是spring.profiles.active属性,依然可以配置多值;
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
进入doGetDefaultProfiles可以看到, 读取的是spring.profiles.default属性,也可以配置多值;
protected Set<String> doGetDefaultProfiles() {
synchronized (this.defaultProfiles) {
if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.defaultProfiles;
}
}
接触过Spring Boot的话,想必对spring.profiles.active不会陌生。
硬编码配置Profile
接口ConfigurableEnvironment提供了修改ActiveProfiles和DefaultProfiles的方法,所以除了属性配置,也可以通过硬编码的方式改变:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("testa_bean_config.xml", TestA.class);
assertFalse(ctx.containsBean("bookShelf1"));
ctx.getEnvironment().setActiveProfiles("dev");
ctx.refresh();
assertTrue(ctx.containsBean("bookShelf1"));
XML 配置Profile的限制
- profile的定义比较简单,多个值在一个beans元素上,相当于or,任意一个满足条件即可
- 可以使用非操作
- 可以通过beans元素嵌套的方式来达成and逻辑, 下面是官网的例子
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
</beans>
@Profile注解支持表达式要比XML配置更加灵活
- !: A logical “not” of the profile
- &: A logical “and” of the profiles
- |: A logical “or” of the profiles
- 可以通过()组合成比较长的逻辑,比如:production & (us-east | eu-central)
当前先只是通过简单的XML配置来初步了解Spring的工作原理,后续再来学习注解部分。
|