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