介绍
Environment架构是spring 3.1版本引入的,它代表当前应用运行环境比如servlet,porlet
。主要处理两部分的内容 profiles和properties,properties将使用org.springframework.core.env.PropertyResolver解析。而Environment继承了此实现。
背景
一个profile是有名字的,在激活状态下它代表注册在容器中的一组bean definitions.一组bean可以通过xml和注解配置被归于到一个profile中,只要这个profile被激活了那这组bean则是可以被注册的。那Environment则担任了访问profile的角色。
Properties 几乎在所有的应用中都担任着重要角色。可能来源于properties file,JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps等等。那Environment则为用户提供了方便的接口以配置property Source和通过它们解析属性。
使用EnvironmentAware访问
可以在ApplicationContext 中为定义一个实现EnvironmentAware接口或@inject来了解这个Environment
在多数情况下,应用中的bean不需要与Enviroment直接交互,相反我们声明诸如PropertySourcesPlaceholderConfigurer的beanFactory处理器(本身实现了EnvironmentAware接口)和<context:property-placeholder/>来解析${}
如果想修改ActiveProfile和Properties则使用ConfigurableEnvironment
理解property sources
Environment架构提供了在一组有层次的property sources上进行迭代查询操作。便于理解,请看下面的例子
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 代表简简单单的键值对。在默认DefaultEnvironment 下,spring配置了两种PropertySource ----一种代表JVM系统属性等价于System.getProperties()返回属性集;另一种代表系统环境变量等价于System.getenv()属性集。就是说如果一个'foo'JVM系统属性或一个'foo'环境变量出现在运行时,那env.containsProperty("foo")调用
将返回true.
而且搜素是具有先后概念的,默认情况下系统属性优先于环境变量。如果'foo'同时出现在JVM系统属性和环境变量中,则系统属性'foo'将胜出。
更重要的是搜素顺序是可以定制的,当想要自定义属性集插入到搜索序列中时可以实现PropertySource 接口并添加到Environment的一组PropertySources中。
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
以上片段中MyPropertySource 被添加到搜索序列中且具有最高优先级。如果它包含'foo',则立即返回不用继续搜索其他的PropertySource 。
MutablePropertySources API提供了更精细化的方法管理一组PropertySource,将在最后介绍
注意:在单独的Application应用中,默认使用DefaultEnvironment;
在web应用中,则使用DefaultWebEnvironment,它在DefaultEnvironment基础上多了servletContext和servletConfig初始化参数
在porlet应用中,则使用DefaultPortletEnvironment,它在DefaultEnvironment基础上 多了porletContext和porletConfig初始化参数;
以上两种可以启用JndiPropertySource
使用property sources
理解property sources基本概念和与Environment的关系之后,如何将这些运用到开发中来,考虑几个场景
场景1:${placeholder}在<import/> 中
假如有几个特定于某些用户的spring配置文件,那么我们可以有条件的载入这些文件通过'customer' 属性值
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
spring 3.1之前,import元素中占位符的值只能依赖于JVM system properties 和environment variables,现在就不是这样。Environment架构完全整合到容器中,通过它很容器解决属性占位符路由问题。就是说可以控制搜索过程,比如改变JVM system properties 和environment variables优先级、完全删除它们、添加自己的Property Source。
注意:<import/>元素的处理在BeanFactoryPostProcessors 调用之前,就是说PropertyPlaceholderConfigurer在这无效,因为environment对象和他的一组property source在refresh之前所以在<import/>中的占位符将被正确解析,没有任何生命周期问题。
场景2:${placeholder}在bean definition
多数开发人员熟悉使用PropertyPlaceholderConfigurer 或者 <context:property-placeholder/>替换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,这个组件仍然会查看datasource.properties以解析${database.*},如果没找到则在当前Environment 中查找。当然我们可以改变这种顺序。
注意:在某些情况下,<context:property-placeholder/>依旧会注册PropertyPlaceholderConfigurer,在spring-context schema 3.1版本中,system-properties-mode属性已经移除掉。这是因为这个属性在PropertySources-/Environment世界里不再起作用,但如果使用spring3.1开发,而schema则使用spring-context-3.0.xsd并且设置了system-properties-mode,那<context:property-placeholder>会注册PropertyPlaceholderConfigurer。
<context:property-placeholder system-properties-mode=""/>
在web应用中操纵 property sources
目前为止我们都是通过编程式的访问ApplicationContext操纵property sources
事实上很多spring应用时基于web的,使用ContextLoaderListener管理ApplicationContext。因此引入ApplicationContextInitializer 和它的伙伴servlet context 参数contextInitializerClasses(指定多个,以逗号分隔)
<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时提供了一种方便的方式在容器refresh之前和ApplicationContext交互。正好在这操纵property sources,也可以设置bean.xml setConfigLocations