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


新的屬性管理API

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

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

Environment:環境,本身是一個PropertyResolver,但是提供了Profile特性,即可以根據環境得到相應數據(即激活不同的Profile,可以得到不同的屬性數據,比如用於多環境場景的配置(正式機、測試機、開發機DataSource配置))

Profile:剖面,只有激活的剖面的組件/配置才會注冊到Spring容器,類似於maven中profile

 

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

PropertySource

key-value對,API如下所示:

1 public String getName()  //屬性源的名字
2 public T getSource()        //屬性源(比如來自Map,那就是一個Map對象)
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())。

在程序中通過如下代碼注入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屬性,常見的有三種方式:

一、啟動Java應用時,通過-D傳入系統參數

-Dspring.profiles.active=dev

二、如果是web環境,可以通過上下文初始化參數設置

spring.profiles.active=dev

三 、通過自定義添加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);

四、直接設置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來進行屬性管理吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM