Spring3.1提供了新的属性管理API,并且功能很是强大且很完善,对于一些属性配置信息都应该使用新的API来管理。虽然如今Spring已经到4版本了,这篇文章来的晚点。html
PropertySource:属性源,key-value属性对抽象,好比用于配置数据java
PropertyResolver:属性解析器,用于解析相应key的valuegit
Environment:环境,自己是一个PropertyResolver,可是提供了Profile特性,便可以根据环境获得相应数据(即激活不一样的Profile,能够获得不一样的属性数据,好比用于多环境场景的配置(正式机、测试机、开发机DataSource配置))github
Profile:剖面,只有激活的剖面的组件/配置才会注册到Spring容器,相似于maven中profileweb
也就是说,新的API主要从配置属性、解析属性、不一样环境解析不一样的属性、激活哪些组件/配置进行注册这几个方面进行了从新设计,使得API的目的更加清晰,并且功能更增强大。spring
key-value对,API以下所示:数据库
public String getName() //属性源的名字 spring-mvc
public T getSource() //属性源(好比来自Map,那就是一个Map对象) mvc
public boolean containsProperty(String name) //是否包含某个属性
public abstract Object getProperty(String name) //获得属性名对应的属性值
很是相似于Map;用例以下:
public void test() throws IOException {
Map<String, Object> map = new HashMap<>();
map.put("encoding", "gbk");
PropertySource propertySource1 = new MapPropertySource("map", map);
System.out.println(propertySource1.getProperty("encoding"));
ResourcePropertySource propertySource2 = new ResourcePropertySource("resource", "classpath:resources.properties"); //name, location
System.out.println(propertySource2.getProperty("encoding"));
}
MapPropertySource的属性来自于一个Map,而ResourcePropertySource的属性来自于一个properties文件,另外还有如PropertiesPropertySource,其属性来自Properties,ServletContextPropertySource的属性来自ServletContext上下文初始化参数等等,你们能够查找PropertySource的继承层次查找相应实现。
public void test2() throws IOException {
//省略propertySource1/propertySource2
CompositePropertySource compositePropertySource = new CompositePropertySource("composite");
compositePropertySource.addPropertySource(propertySource1);
compositePropertySource.addPropertySource(propertySource2);
System.out.println(compositePropertySource.getProperty("encoding"));
}
CompositePropertySource提供了组合PropertySource的功能,查找顺序就是注册顺序。
另外还有一个PropertySources,从名字能够看出其包含多个PropertySource:
public interface PropertySources extends Iterable<PropertySource<?>> {
boolean contains(String name); //是否包含某个name的PropertySource
PropertySource<?> get(String name); //根据name找到PropertySource
}
示例以下:
public void test3() throws IOException {
//省略propertySource1/propertySource2
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource1);
propertySources.addLast(propertySource2);
System.out.println(propertySources.get("resource").getProperty("encoding"));
for(PropertySource propertySource : propertySources) {
System.out.println(propertySource.getProperty("encoding"));
}
}
默认提供了一个MutablePropertySources实现,咱们能够调用addFirst添加到列表的开头,addLast添加到末尾,另外能够经过addBefore(propertySourceName, propertySource)或addAfter(propertySourceName, propertySource)添加到某个propertySource前面/后面;最后你们能够经过iterator迭代它,而后按照顺序获取属性。
到目前咱们已经有属性了,接下来须要更好的API来解析属性了。
属性解析器,用来根据名字解析其值等。API以下所示:
public interface PropertyResolver {
//是否包含某个属性
boolean containsProperty(String key);
//获取属性值 若是找不到返回null
String getProperty(String key);
//获取属性值,若是找不到返回默认值
String getProperty(String key, String defaultValue);
//获取指定类型的属性值,找不到返回null
<T> T getProperty(String key, Class<T> targetType);
//获取指定类型的属性值,找不到返回默认值
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
//获取属性值为某个Class类型,找不到返回null,若是类型不兼容将抛出ConversionException
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
//获取属性值,找不到抛出异常IllegalStateException
String getRequiredProperty(String key) throws IllegalStateException;
//获取指定类型的属性值,找不到抛出异常IllegalStateException
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
//替换文本中的占位符(${key})到属性值,找不到不解析
String resolvePlaceholders(String text);
//替换文本中的占位符(${key})到属性值,找不到抛出异常IllegalArgumentException
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
从API上咱们已经看出解析器的做用了,具体功能就不要罗嗦了。示例以下:
public void test() throws Exception {
//省略propertySources
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
System.out.println(propertyResolver.getProperty("encoding"));
System.out.println(propertyResolver.getProperty("no", "default"));
System.out.println(propertyResolver.resolvePlaceholders("must be encoding ${encoding}")); //输出must be encoding gbk
}
从如上示例能够看出其很是简单。另外Environment也继承了PropertyResolver。
环境,好比JDK环境,Servlet环境,Spring环境等等;每一个环境都有本身的配置数据,如System.getProperties()、System.getenv()等能够拿到JDK环境数据;ServletContext.getInitParameter()能够拿到Servlet环境配置数据等等;也就是说Spring抽象了一个Environment来表示环境配置。
public interface Environment extends PropertyResolver {//继承PropertyResolver
//获得当前明确激活的剖面
String[] getActiveProfiles();
//获得默认激活的剖面,而不是明确设置激活的
String[] getDefaultProfiles();
//是否接受某些剖面
boolean acceptsProfiles(String... profiles);
}
从API上能够看出,除了能够解析相应的属性信息外,还提供了剖面相关的API,目的是: 能够根据剖面有选择的进行注册组件/配置。好比对于不一样的环境注册不一样的组件/配置(正式机、测试机、开发机等的数据源配置)。它的主要几个实现以下所示:
MockEnvironment:模拟的环境,用于测试时使用;
StandardEnvironment:标准环境,普通Java应用时使用,会自动注册System.getProperties() 和 System.getenv()到环境;
StandardServletEnvironment:标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet)、ServletContext及JNDI实例到环境;
除了这些,咱们也能够根据需求定义本身的Environment。示例以下:
public void test() {
//会自动注册 System.getProperties() 和 System.getenv()
Environment environment = new StandardEnvironment();
System.out.println(environment.getProperty("file.encoding"));
}
其默认有两个属性:systemProperties(System.getProperties())和systemEnvironment(System.getenv())。
在web环境中首先在web.xml中配置:
<context-param>
<param-name>myConfig</param-name>
<param-value>hello</param-value>
</context-param>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
使用StandardServletEnvironment加载时,默认除了StandardEnvironment的两个属性外,还有另外三个属性:servletContextInitParams(ServletContext)、servletConfigInitParams(ServletConfig)、jndiProperties(JNDI)。
而后在程序中经过以下代码注入Environment:
@Autowired
Environment env;
另外也能够直接使用ApplicationContext.getEnvironment()获取;接着就能够用以下代码获取配置:
System.out.println(env.getProperty("myConfig"));
System.out.println(env.getProperty("contextConfigLocation"));
另外咱们在运行应用时能够经过-D传入系统参数(System.getProperty()),如java -Ddata=123 com.sishuok.spring3.EnvironmentTest,那么咱们能够经过environment.getProperty("data") 获取到。
若是咱们拿到的上下文是ConfigurableApplicationContext类型,那么能够:ctx.getEnvironment().getPropertySources() ;而后经过PropertySources再添加自定义的PropertySource。
profile,剖面,大致意思是:咱们程序可能从某几个剖面来执行应用,好比正式机环境、测试机环境、开发机环境等,每一个剖面的配置可能不同(好比开发机可能使用本地的数据库测试,正式机使用正式机的数据库测试)等;所以呢,就须要根据不一样的环境选择不一样的配置;若是用过maven,maven中就有profile的概念。
profile有两种:
默认的:经过“spring.profiles.default”属性获取,若是没有配置默认值是“default”
明确激活的:经过“spring.profiles.active”获取
查找顺序是:先进性明确激活的匹配,若是没有指定明确激活的(即集合为空)就找默认的;配置属性值从Environment读取。
API请参考Environment部分。设置profile属性,常见的有三种方式:
1、启动Java应用时,经过-D传入系统参数
-Dspring.profiles.active=dev
2、若是是web环境,能够经过上下文初始化参数设置
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>
三 、经过自定义添加PropertySource
Map<String, Object> map = new HashMap<String, Object>();
map.put("spring.profiles.active", "dev");
MapPropertySource propertySource = new MapPropertySource("map", map);
env.getPropertySources().addFirst(propertySource);
4、直接设置Profile
env.setActiveProfiles("dev", "test");
以上方式均可以设置多个profile,多个之间经过如逗号/分号等分隔。
接着咱们就能够经过以下API判断是否激活相应的Profile了:
if(env.acceptsProfiles("dev", "test"))) {
//do something
}
它们之间是或的关系;即找到一个便可;若是有人想不匹配某个profile执行某些事情,能够经过如"!dev" 即没有dev激活时返回true。
固然这种方式还不是太友好,还须要咱们手工编程使用,稍候会介绍如何更好的使用它们。
${key}占位符属性替换器,配置以下:
<context:property-placeholder
location="属性文件,多个之间逗号分隔"
file-encoding="文件编码"
ignore-resource-not-found="是否忽略找不到的属性文件"
ignore-unresolvable="是否忽略解析不到的属性,若是不忽略,找不到将抛出异常"
properties-ref="本地Properties配置"
local-override="是否本地覆盖模式,即若是true,那么properties-ref的属性将覆盖location加载的属性,不然相反"
system-properties-mode="系统属性模式,默认ENVIRONMENT(表示先找ENVIRONMENT,再找properties-ref/location的),NEVER:表示永远不用ENVIRONMENT的,OVERRIDE相似于ENVIRONMENT"
order="顺序"
/>
location:表示属性文件位置,多个之间经过如逗号/分号等分隔;
file-encoding:文件编码;
ignore-resource-not-found:若是属性文件找不到,是否忽略,默认false,即不忽略,找不到将抛出异常
ignore-unresolvable:是否忽略解析不到的属性,若是不忽略,找不到将抛出异常
properties-ref:本地java.util.Properties配置
local-override:是否本地覆盖模式,即若是true,那么properties-ref的属性将覆盖location加载的属性
system-properties-mode:系统属性模式,ENVIRONMENT(默认),NEVER,OVERRIDE
ENVIRONMENT:将使用Spring 3.1提供的PropertySourcesPlaceholderConfigurer,其余状况使用Spring 3.1以前的PropertyPlaceholderConfigurer
若是是本地覆盖模式:那么查找顺序是:properties-ref、location、environment,不然正好反过来;
OVERRIDE: PropertyPlaceholderConfigurer使用,由于在spring 3.1以前版本是没有Enviroment的,因此OVERRIDE是spring 3.1以前版本的Environment
若是是本地覆盖模式:那么查找顺序是:properties-ref、location、System.getProperty(),System.getenv(),不然正好反过来;
NEVER:只查找properties-ref、location;
order:当配置多个<context:property-placeholder/>时的查找顺序,关于顺序问题请参考:http://www.iteye.com/topic/1131688
具体使用请参考以下文件中的如dataSource:
https://github.com/zhangkaitao/es/blob/master/web/src/main/resources/spring-config.xml
Spring 3.1提供的Java Config方式的注解,其属性会自动注册到相应的Environment;如:
@Configuration
@PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
public class AppConfig {
}
接着就可使用env.getProperty("encoding")获得相应的属性值。
另外若是想进行Bean属性的占位符替换,须要注册PropertySourcesPlaceholderConfigurer:
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
如上配置等价于XML中的<context:property-placeholder/>配置。
若是想导入多个,在Java8以前须要使用@PropertySources注册多个@PropertySource()。
此处要注意:
使用<context:property-placeholder/>不会自动把属性注册到Environment中,而@PropertySource()会;且在XML配置中并无@PropertySource()等价的XML命名空间配置,若是须要,能够本身写一个。
使用Environment属性替换,如:
<context:property-placeholder location="classpath:${env}/resources.properties"/>
<context:component-scan base-package="com.sishuok.${package}"/>
<import resource="classpath:${env}/ctx.xml"/>
@PropertySource(value = "classpath:${env}/resources.properties")
@ComponentScan(basePackages = "com.sishuok.${package}")
@ImportResource(value = {"classpath:${env}/cfg.xml"})
@Value("${env}")
new ClassPathXmlApplicationContext("classpath:${env}/cfg.xml")
使用PropertySourcesPlaceholderConfigurer / PropertyPlaceholderConfigurer进性Bean属性替换,如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${connection.url}"/>
<property name="username" value="${connection.username}"/>
<property name="password" value="${connection.password}"/>
</bean>
SpEL表达式:
请参考【第五章】Spring表达式语言 之 5.4在Bean定义中使用EL—跟我学spring3
经过如上方式能够实现不一样的环境有不一样的属性配置,可是若是咱们想不一样的环境加载不一样的Bean呢,好比测试机/正式机环境可能使用远程方式访问某些API,而开发机环境使用本地方式进行开发,提升开发速度,这就须要profile了。
经过在beans标签上加上profile属性,这样当咱们激活相应的profile时,此beans标签下的bean就会注册,以下所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans profile="dev">
<bean id="dataSource" class="本地DataSource">
</bean>
</beans>
<beans profile="test">
<bean id="dataSource" class="测试环境DataSource">
</bean>
</beans>
</beans>
启动应用时设置相应的“spring.profiles.active”便可。另外,若是想指定一个默认的,可使用<beans profile="default">指定(若是不是default,能够经过“spring.profiles.default”指定)。
Java Config方式的Profile,功能等价于XML中的<beans profiles>,使用方式以下:
@Profile("dev")
@Configuration
@PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
public class AppConfig {
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Spring4提供了一个新的@Conditional注解,请参考http://jinnianshilongnian.iteye.com/blog/1989379。
在测试时,有时候不能经过系统启动参数/上下文参数等指定Profile,此时Spring测试框架提供了@ActiveProfiles()注解,示例以下:
@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = GenericConfig.class)
public class GenricInjectTest {
……
}
经过这种方式,咱们就激活了test profile。
到此整个Spring的属性管理API就介绍完了,对于属性管理,核心是Environment,因此之后请使用Environment来进行属性管理吧。