领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在本系列 前两篇 博文 中,我介绍了 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 系统属性集(类似于 System.getProperties()),另一个代表系统环境变量集(类似于 System.getenv())[1]。这意味着,如果在运行时存在“foo”系统属性或“foo”环境变量,则调用 env.containsProperty("foo") 将返回 true。
执行的搜索是分层的。默认情况下,系统属性的优先级高于环境变量,因此如果在调用 env.getProperty("foo") 时,“foo”属性恰好在这两个地方都已设置,那么系统属性值将“获胜”,并优先于环境变量返回。
最重要的是,整个机制是可配置的。也许您有自定义的属性来源,您想将其集成到此搜索中。没问题——只需实现并实例化您自己的 PropertySource,并将其添加到当前 Environment 的 PropertySources 集中。
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 用户都会熟悉使用 PropertyPlaceholderConfigurer 或 context: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.*} 占位符,但如果属性未在文件中找到,它将回退到当前 Environment 的 PropertySources 集。这再次为您提供了更多的控制权;在此更改之前,唯一的后备选项是系统属性和环境变量。
到目前为止,我们已经看到了如何在具有对 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 进行解析,而不会出现任何生命周期问题。Environment 进行解析。
[3]:在某些情况下,context:property-placeholder/ 仍会注册一个 PropertyPlaceholderConfigurer。在 Spring 3.1 版本的 spring-context 模式中,system-properties-mode 属性已从 property-placeholder 元素中移除。这是因为在支持 PropertySources/Environment 的世界中,此属性不再有意义。但是,如果您使用 Spring 3.1 构建但仍使用 spring-context-3.0.xsd 模式并设置了 system-properties-mode 属性,那么 context:property-placeholder 将恢复注册 PropertyPlaceholderConfigurer,以遵循此设置的精确语义。无论如何,这种方法都保留了向后兼容性。