Spring源码学习篇3 - XML配置了解 Profiles

2021/7/14 1:05:45

本文主要是介绍Spring源码学习篇3 - XML配置了解 Profiles,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

开篇

对于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="...">

    <!-- other bean definitions -->

    <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) {
	// Any nested <beans> elements will cause recursion in this method. In
	// order to propagate and preserve <beans> default-* attributes correctly,
	// keep track of the current (parent) delegate, which may be null. Create
	// the new (child) delegate with a reference to the parent for fallback purposes,
	// then ultimately reset this.delegate back to its original (parent) reference.
	// this behavior emulates a stack of delegates without actually necessitating one.
	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);
			// We cannot use Profiles.of(...) since profile expressions are not supported
			// in XML config. See SPR-12458 for details.
			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的限制

  1. profile的定义比较简单,多个值在一个beans元素上,相当于or,任意一个满足条件即可
  2. 可以使用非操作
  3. 可以通过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="...">
    
        <!-- other bean definitions -->
    
        <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的工作原理,后续再来学习注解部分。



这篇关于Spring源码学习篇3 - XML配置了解 Profiles的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程