抢先一步
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 系统属性集 (a la System.getProperties()
),另一个代表系统环境变量集 (a la 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
进行解析,没有任何生命周期问题。
[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
,以遵循此设置的精确语义。这种方法在任何情况下都保留了向后兼容性。