Spring3.1新屬性管理API:PropertySource、Environment、Profi

Spring3.1提供了新的屬性管理API,並且功能很是強大且很完善,對於一些屬性配置信息都應該使用新的API來管理。雖然如今Spring已經到4版本了,這篇文章來的晚點。html

 

新的屬性管理API

PropertySource:屬性源,key-value屬性對抽象,好比用於配置數據java

PropertyResolver:屬性解析器,用於解析相應key的valuegit

Environment:環境,自己是一個PropertyResolver,可是提供了Profile特性,便可以根據環境獲得相應數據(即激活不一樣的Profile,能夠獲得不一樣的屬性數據,好比用於多環境場景的配置(正式機、測試機、開發機DataSource配置))github

Profile:剖面,只有激活的剖面的組件/配置纔會註冊到Spring容器,相似於maven中profileweb

 

也就是說,新的API主要從配置屬性、解析屬性、不一樣環境解析不一樣的屬性、激活哪些組件/配置進行註冊這幾個方面進行了從新設計,使得API的目的更加清晰,並且功能更增強大。spring

 

PropertySource

key-value對,API以下所示:數據庫

查看複製到剪貼板打印編程

  1. public String getName()  //屬性源的名字  spring-mvc

  2. public T getSource()        //屬性源(好比來自Map,那就是一個Map對象)  mvc

  3. public boolean containsProperty(String name)  //是否包含某個屬性  

  4. public abstract Object getProperty(String name)   //獲得屬性名對應的屬性值  

很是相似於Map;用例以下:

查看複製到剪貼板打印

  1. @Test   

  2. public void test() throws IOException {  

  3.     Map<String, Object> map = new HashMap<>();  

  4.     map.put("encoding""gbk");  

  5.     PropertySource propertySource1 = new MapPropertySource("map", map);  

  6.     System.out.println(propertySource1.getProperty("encoding"));  

  7.   

  8.     ResourcePropertySource propertySource2 = new ResourcePropertySource("resource""classpath:resources.properties"); //name, location  

  9.     System.out.println(propertySource2.getProperty("encoding"));  

  10. }  

MapPropertySource的屬性來自於一個Map,而ResourcePropertySource的屬性來自於一個properties文件,另外還有如PropertiesPropertySource,其屬性來自Properties,ServletContextPropertySource的屬性來自ServletContext上下文初始化參數等等,你們能夠查找PropertySource的繼承層次查找相應實現。

 

查看複製到剪貼板打印

  1. @Test   

  2. public void test2() throws IOException {  

  3.     //省略propertySource1/propertySource2  

  4.     CompositePropertySource compositePropertySource = new CompositePropertySource("composite");  

  5.     compositePropertySource.addPropertySource(propertySource1);  

  6.     compositePropertySource.addPropertySource(propertySource2);  

  7.     System.out.println(compositePropertySource.getProperty("encoding"));  

  8. }  

CompositePropertySource提供了組合PropertySource的功能,查找順序就是註冊順序。 

 

另外還有一個PropertySources,從名字能夠看出其包含多個PropertySource:

查看複製到剪貼板打印

  1. public interface PropertySources extends Iterable<PropertySource<?>> {  

  2.     boolean contains(String name); //是否包含某個name的PropertySource  

  3.     PropertySource<?> get(String name); //根據name找到PropertySource  

  4. }  

 

示例以下:

查看複製到剪貼板打印

  1. @Test   

  2. public void test3() throws IOException {  

  3.     //省略propertySource1/propertySource2  

  4.     MutablePropertySources propertySources = new MutablePropertySources();  

  5.     propertySources.addFirst(propertySource1);  

  6.     propertySources.addLast(propertySource2);  

  7.     System.out.println(propertySources.get("resource").getProperty("encoding"));  

  8.   

  9.     for(PropertySource propertySource : propertySources) {  

  10.         System.out.println(propertySource.getProperty("encoding"));  

  11.     }  

  12. }  

默認提供了一個MutablePropertySources實現,咱們能夠調用addFirst添加到列表的開頭,addLast添加到末尾,另外能夠經過addBefore(propertySourceName, propertySource)或addAfter(propertySourceName, propertySource)添加到某個propertySource前面/後面;最後你們能夠經過iterator迭代它,而後按照順序獲取屬性。

 

到目前咱們已經有屬性了,接下來須要更好的API來解析屬性了。

 

PropertyResolver

屬性解析器,用來根據名字解析其值等。API以下所示:

查看複製到剪貼板打印

  1. public interface PropertyResolver {  

  2.   

  3.     //是否包含某個屬性  

  4.     boolean containsProperty(String key);  

  5.    

  6.         //獲取屬性值 若是找不到返回null   

  7.     String getProperty(String key);  

  8.        

  9.         //獲取屬性值,若是找不到返回默認值        

  10.     String getProperty(String key, String defaultValue);  

  11.     

  12.         //獲取指定類型的屬性值,找不到返回null  

  13.     <T> T getProperty(String key, Class<T> targetType);  

  14.   

  15.         //獲取指定類型的屬性值,找不到返回默認值  

  16.     <T> T getProperty(String key, Class<T> targetType, T defaultValue);  

  17.   

  18.          //獲取屬性值爲某個Class類型,找不到返回null,若是類型不兼容將拋出ConversionException  

  19.     <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);  

  20.   

  21.         //獲取屬性值,找不到拋出異常IllegalStateException  

  22.     String getRequiredProperty(String key) throws IllegalStateException;  

  23.   

  24.         //獲取指定類型的屬性值,找不到拋出異常IllegalStateException         

  25.     <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;  

  26.   

  27.         //替換文本中的佔位符(${key})到屬性值,找不到不解析  

  28.     String resolvePlaceholders(String text);  

  29.   

  30.         //替換文本中的佔位符(${key})到屬性值,找不到拋出異常IllegalArgumentException  

  31.     String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;  

  32.   

  33. }  

從API上咱們已經看出解析器的做用了,具體功能就不要羅嗦了。示例以下:

查看複製到剪貼板打印

  1. @Test   

  2. public void test() throws Exception {  

  3.     //省略propertySources  

  4.   

  5.     PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);  

  6.   

  7.     System.out.println(propertyResolver.getProperty("encoding"));  

  8.     System.out.println(propertyResolver.getProperty("no""default"));  

  9.     System.out.println(propertyResolver.resolvePlaceholders("must be encoding ${encoding}"));  //輸出must be encoding gbk  

  10. }  

從如上示例能夠看出其很是簡單。另外Environment也繼承了PropertyResolver。

 

Environment

 環境,好比JDK環境,Servlet環境,Spring環境等等;每一個環境都有本身的配置數據,如System.getProperties()、System.getenv()等能夠拿到JDK環境數據;ServletContext.getInitParameter()能夠拿到Servlet環境配置數據等等;也就是說Spring抽象了一個Environment來表示環境配置。

查看複製到剪貼板打印

  1. public interface Environment extends PropertyResolver {//繼承PropertyResolver  

  2.   

  3.         //獲得當前明確激活的剖面  

  4.     String[] getActiveProfiles();  

  5.   

  6.         //獲得默認激活的剖面,而不是明確設置激活的  

  7.     String[] getDefaultProfiles();  

  8.    

  9.         //是否接受某些剖面  

  10.     boolean acceptsProfiles(String... profiles);  

  11.   

  12. }  

從API上能夠看出,除了能夠解析相應的屬性信息外,還提供了剖面相關的API,目的是: 能夠根據剖面有選擇的進行註冊組件/配置。好比對於不一樣的環境註冊不一樣的組件/配置(正式機、測試機、開發機等的數據源配置)。它的主要幾個實現以下所示:

 

MockEnvironment:模擬的環境,用於測試時使用;

StandardEnvironment:標準環境,普通Java應用時使用,會自動註冊System.getProperties() 和 System.getenv()到環境;

StandardServletEnvironment:標準Servlet環境,其繼承了StandardEnvironment,Web應用時使用,除了StandardEnvironment外,會自動註冊ServletConfig(DispatcherServlet)、ServletContext及JNDI實例到環境;

除了這些,咱們也能夠根據需求定義本身的Environment。示例以下:

查看複製到剪貼板打印

  1. @Test   

  2. public void test() {  

  3.     //會自動註冊 System.getProperties() 和 System.getenv()  

  4.     Environment environment = new StandardEnvironment();  

  5.     System.out.println(environment.getProperty("file.encoding"));  

  6. }  

其默認有兩個屬性:systemProperties(System.getProperties())和systemEnvironment(System.getenv())。

 

在web環境中首先在web.xml中配置:

查看複製到剪貼板打印

  1. <context-param>  

  2.     <param-name>myConfig</param-name>  

  3.     <param-value>hello</param-value>  

  4. </context-param>  

  5.   

  6. <servlet>  

  7.     <servlet-name>spring</servlet-name>  

  8.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  

  9.     <init-param>  

  10.         <param-name>contextConfigLocation</param-name>  

  11.         <param-value>classpath:spring-mvc.xml</param-value>  

  12.     </init-param>  

  13. </servlet>  

使用StandardServletEnvironment加載時,默認除了StandardEnvironment的兩個屬性外,還有另外三個屬性:servletContextInitParams(ServletContext)、servletConfigInitParams(ServletConfig)、jndiProperties(JNDI)。

 

而後在程序中經過以下代碼注入Environment: 

查看複製到剪貼板打印

  1. @Autowired  

  2. Environment env;  

另外也能夠直接使用ApplicationContext.getEnvironment()獲取;接着就能夠用以下代碼獲取配置:

查看複製到剪貼板打印

  1. System.out.println(env.getProperty("myConfig"));  

  2. 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

profile,剖面,大致意思是:咱們程序可能從某幾個剖面來執行應用,好比正式機環境、測試機環境、開發機環境等,每一個剖面的配置可能不同(好比開發機可能使用本地的數據庫測試,正式機使用正式機的數據庫測試)等;所以呢,就須要根據不一樣的環境選擇不一樣的配置;若是用過maven,maven中就有profile的概念。

 

profile有兩種:

默認的:經過「spring.profiles.default」屬性獲取,若是沒有配置默認值是「default」

明確激活的:經過「spring.profiles.active」獲取

查找順序是:先進性明確激活的匹配,若是沒有指定明確激活的(即集合爲空)就找默認的;配置屬性值從Environment讀取。

 

API請參考Environment部分。設置profile屬性,常見的有三種方式:

1、啓動Java應用時,經過-D傳入系統參數

查看複製到剪貼板打印

  1. -Dspring.profiles.active=dev  

 

2、若是是web環境,能夠經過上下文初始化參數設置

查看複製到剪貼板打印

  1. <context-param>  

  2.     <param-name>spring.profiles.active</param-name>  

  3.     <param-value>dev</param-value>  

  4. </context-param>  

 

三 、經過自定義添加PropertySource

查看複製到剪貼板打印

  1. Map<String, Object> map = new HashMap<String, Object>();  

  2. map.put("spring.profiles.active""dev");  

  3. MapPropertySource propertySource = new MapPropertySource("map", map);  

  4. env.getPropertySources().addFirst(propertySource);  

 

4、直接設置Profile

查看複製到剪貼板打印

  1. env.setActiveProfiles("dev""test");  

以上方式均可以設置多個profile,多個之間經過如逗號/分號等分隔。  

 

接着咱們就能夠經過以下API判斷是否激活相應的Profile了: 

查看複製到剪貼板打印

  1. if(env.acceptsProfiles("dev""test"))) {  

  2.     //do something  

  3. }  

它們之間是或的關係;即找到一個便可;若是有人想不匹配某個profile執行某些事情,能夠經過如"!dev"  即沒有dev激活時返回true。

 

固然這種方式還不是太友好,還須要咱們手工編程使用,稍候會介紹如何更好的使用它們。

 

<context:property-placeholder/>

${key}佔位符屬性替換器,配置以下:

查看複製到剪貼板打印

  1. <context:property-placeholder   

  2.         location="屬性文件,多個之間逗號分隔"  

  3.         file-encoding="文件編碼"  

  4.         ignore-resource-not-found="是否忽略找不到的屬性文件"  

  5.         ignore-unresolvable="是否忽略解析不到的屬性,若是不忽略,找不到將拋出異常"  

  6.         properties-ref="本地Properties配置"  

  7.         local-override="是否本地覆蓋模式,即若是true,那麼properties-ref的屬性將覆蓋location加載的屬性,不然相反"  

  8.         system-properties-mode="系統屬性模式,默認ENVIRONMENT(表示先找ENVIRONMENT,再找properties-ref/location的),NEVER:表示永遠不用ENVIRONMENT的,OVERRIDE相似於ENVIRONMENT"  

  9.         order="順序"  

  10.         />  

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

 

@PropertySource()

Spring 3.1提供的Java Config方式的註解,其屬性會自動註冊到相應的Environment;如:

查看複製到剪貼板打印

  1. @Configuration  

  2. @PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)  

  3. public class AppConfig {  

  4. }  

 

接着就可使用env.getProperty("encoding")獲得相應的屬性值。 

 

另外若是想進行Bean屬性的佔位符替換,須要註冊PropertySourcesPlaceholderConfigurer:

查看複製到剪貼板打印

  1. @Bean   

  2. public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {  

  3.     return new PropertySourcesPlaceholderConfigurer();  

  4. }  

如上配置等價於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屬性替換,如:

查看複製到剪貼板打印

  1. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">  

  2.         <!-- 基本屬性 url、user、password -->  

  3.         <property name="url" value="${connection.url}"/>  

  4.         <property name="username" value="${connection.username}"/>  

  5.         <property name="password" value="${connection.password}"/>  

  6. </bean>  

 

 

SpEL表達式:

請參考【第五章】Spring表達式語言 之 5.4在Bean定義中使用EL—跟我學spring3

 

經過如上方式能夠實現不一樣的環境有不一樣的屬性配置,可是若是咱們想不一樣的環境加載不一樣的Bean呢,好比測試機/正式機環境可能使用遠程方式訪問某些API,而開發機環境使用本地方式進行開發,提升開發速度,這就須要profile了。

 

<beans  profile="">

經過在beans標籤上加上profile屬性,這樣當咱們激活相應的profile時,此beans標籤下的bean就會註冊,以下所示:

查看複製到剪貼板打印

  1. <beans xmlns="http://www.springframework.org/schema/beans"  

  2.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  

  3.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">  

  4.   

  5. <beans profile="dev">  

  6.     <bean id="dataSource" class="本地DataSource">  

  7.     </bean>              

  8. </beans>  

  9.   

  10. <beans profile="test">  

  11.     <bean id="dataSource" class="測試環境DataSource">  

  12.     </bean>  

  13. </beans>  

  14.   

  15. </beans>  

 

啓動應用時設置相應的「spring.profiles.active」便可。另外,若是想指定一個默認的,可使用<beans profile="default">指定(若是不是default,能夠經過「spring.profiles.default」指定)。

@Profile()

Java Config方式的Profile,功能等價於XML中的<beans profiles>,使用方式以下:

查看複製到剪貼板打印

  1. @Profile("dev")  

  2. @Configuration  

  3. @PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)  

  4. public class AppConfig {  

  5.   

  6.     @Bean   

  7.     public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {  

  8.         return new PropertySourcesPlaceholderConfigurer();  

  9.     }  

  10. }  

Spring4提供了一個新的@Conditional註解,請參考http://jinnianshilongnian.iteye.com/blog/1989379

 

@ActiveProfiles()

在測試時,有時候不能經過系統啓動參數/上下文參數等指定Profile,此時Spring測試框架提供了@ActiveProfiles()註解,示例以下:

查看複製到剪貼板打印

  1. @ActiveProfiles("test")  

  2. @RunWith(SpringJUnit4ClassRunner.class)  

  3. @ContextConfiguration(classes = GenericConfig.class)  

  4. public class GenricInjectTest {  

  5. ……  

  6. }  

經過這種方式,咱們就激活了test profile。 

 

 

到此整個Spring的屬性管理API就介紹完了,對於屬性管理,核心是Environment,因此之後請使用Environment來進行屬性管理吧。

相關文章
相關標籤/搜索