Spring 3.1 M1:统一的属性管理

工程 | Chris Beams | 2011年2月15日 | ...

在本系列的前两篇文章中,我描述了bean 定义配置文件特性,以及它与 Spring 3.1 M1 中新增的 Environment 抽象的关系。今天我们将探讨 Environment 的第二个方面——它是如何帮助简化配置属性管理问题的。

理解属性源

Spring 的 Environment 抽象提供了一个可配置的属性源层次结构的搜索操作。为了充分解释,请考虑以下内容


ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码片段中,我们看到了一种高级的方式来询问 Spring 当前环境中是否定义了 'foo' 属性。为了回答这个问题,Environment 对象会对一组 PropertySource 对象执行搜索。PropertySource 是对任何键值对源的简单抽象,Spring 的 DefaultEnvironment 配置了两个 PropertySource 对象——一个代表 JVM 系统属性集 (a la System.getProperties()),另一个代表系统环境变量集 (a la System.getenv())[1]。这意味着如果在运行时存在 'foo' 系统属性或 'foo' 环境变量,调用 env.containsProperty("foo") 将返回 true

执行的搜索是分层次的。默认情况下,系统属性优先于环境变量,因此如果在调用 env.getProperty("foo") 期间 'foo' 属性在这两个地方都设置了,系统属性值将“获胜”并优先于环境变量返回。

最重要的是,整个机制是可配置的。也许你有一个自定义的属性源,你想将其集成到这个搜索中。没问题——只需实现并实例化你自己的 PropertySource,然后将其添加到当前 EnvironmentPropertySources 集中即可


ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代码中,MyPropertySource 已以最高的优先级添加到搜索中。如果它包含一个 'foo' 属性,它将被检测到并优先于任何其他 PropertySource 中的 'foo' 属性返回。MutablePropertySources API 暴露了许多方法,允许对属性源集进行精确操作。查阅Javadoc 了解完整细节。

使用属性源

现在你已经理解了属性源的基本知识以及它们与 Environment 的关系,你可能想知道这一切作为 Spring 应用开发者来说与你有什么关系。让我们考虑几个场景,看看它们是如何协同工作的。

场景 1: 语句中的 ${placeholder} 解析

你有一组 Spring 配置文件,用于配置特定于应用程序某些客户的 bean,并且你使用包含解析为 'customer' 属性值的占位符的 语句有条件地加载这些文件


<beans>
	<import resource="com/bank/service/${customer}-config.xml"/>
</beans>

在 Spring 3.1 之前, 元素中占位符的值只能通过 JVM 系统属性或环境变量[2]来解析。现在情况不再如此。由于 Environment 抽象已集成到整个容器中,因此很容易通过它路由占位符的解析。这意味着你可以按照自己喜欢的方式配置解析过程:更改搜索系统属性和环境变量的优先级,或完全删除它们;根据需要添加你自己的属性源到混合中。

场景 2:bean 定义中的 ${placeholder} 解析

大多数 Spring 用户都熟悉使用 PropertyPlaceholderConfigurercontext:property-placeholder/ 来替换 Spring bean 定义中的 ${...} 占位符。这是一个典型的配置


<context:property-placeholder location="com/bank/config/datasource.properties"/>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClass" value="${database.driver}"/>
	<property name="jdbcUrl" value="${database.url}"/>
	<property name="username" value="${database.username}"/>
	<property name="password" value="${database.password}"/>
</bean>

从 Spring 3.1 开始,context:property-placeholder/ 不再注册 PropertyPlaceholderConfigurer,而是注册一个 PropertySourcesPlaceholderConfigurer[3]。该组件仍然查找 datasource.properties 文件来解析上述的 ${database.*} 占位符,但如果在文件中找不到属性,则会回退到当前 EnvironmentPropertySources 集。这再次提供了更多的控制;在此更改之前,唯一的备选选项是系统属性和环境变量。

在 Web 应用中操作属性源

到目前为止,我们已经看到了如何在独立的应用程序中访问和操作属性源,在这种应用程序中我们可以通过编程方式访问 ApplicationContext。然而,实际上,许多 Spring 应用程序是 Web 应用,其中 ApplicationContext 由 Spring 的 ContextLoaderListener 为你管理。因此,我们引入了 ApplicationContextInitializer 接口及其配套的 contextInitializerClasses servlet 上下文参数。请看

web.xml


<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>com.bank.MyInitializer</param-value>
</context-param>

public class MyInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
	public void initialize(ConfigurableWebApplicationContext ctx) {
		PropertySource ps = new MyPropertySource();
		ctx.getEnvironment().getPropertySources().addFirst(ps);
		// perform any other initialization of the context ...
	}
}

实现和注册 ApplicationContextInitializer 提供了一种简单的方式来与你的应用程序上下文进行交互,在其刷新之前。这是操作属性源的绝佳位置,但你也可以调用 setConfigLocations(...) 或任何其他设计在 refresh() 之前调用的方法。

总结

Spring 的 Environment 抽象提供了一个单一的位置来配置配置文件属性。配置文件,如之前文章所述,决定了在给定的部署上下文中应注册哪些 bean 定义;本文描述的属性支持提供了对任何属性源的一致抽象,从而在整个应用程序配置中实现更灵活的属性访问和占位符解析。

在本系列的下一篇文章中,我们将看看 Spring 3.1 如何通过 FeatureSpecification 支持实现 100% 基于 Java(即:无需 XML)的应用程序配置——这是对 Spring 3.0 中引入的 @Configuration 类支持的自然演进。

脚注

[1]:这些默认属性源存在于 DefaultEnvironment 中,用于独立应用程序。DefaultWebEnvironment 则包含额外的默认属性源,包括 servlet 配置和 servlet 上下文参数。DefaultPortletEnvironment 同样可以访问 portlet 配置和 portlet 上下文参数作为属性源。两者都可以选择启用 JndiPropertySource。详情请参阅Javadoc

[2]:因为对元素的处理必然发生在 BeanFactoryPostProcessors 被调用之前,这意味着即使是 PropertyPlaceholderConfigurer 在这里也无能为力。由于 Environment 及其 PropertySources 集在容器刷新之前配置,因此元素中的占位符可以针对 Environment 进行解析,没有任何生命周期问题。

[3]:在某些情况下,context:property-placeholder/ 仍然会注册一个 PropertyPlaceholderConfigurer。在 3.1 版本的 spring-context schema 中,system-properties-mode 属性已从 property-placeholder 元素中移除。这是因为在具备 PropertySources/Environment 感知能力的世界中,此属性不再有意义。但是,如果你使用 Spring 3.1 进行构建但仍使用 spring-context-3.0.xsd schema 并设置 system-properties-mode 属性,context:property-placeholder 将会回退到注册 PropertyPlaceholderConfigurer,以遵循此设置的精确语义。这种方法在任何情况下都保留了向后兼容性。

获取 Spring 邮件列表

通过 Spring 邮件列表保持联系

订阅

抢先一步

VMware 提供培训和认证,助你加速前进。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部