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來進行屬性管理吧。