1. 程式人生 > >Spring3.1新屬性管理API:PropertySource、Environment、Profile

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

Spring3.1提供了新的屬性管理API,而且功能非常強大且很完善,對於一些屬性配置資訊都應該使用新的API來管理。雖然現在Spring已經到4版本了,這篇文章來的晚點。

新的屬性管理API

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

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

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

Profile:剖面,只有啟用的剖面的元件/配置才會註冊到Spring容器,類似於maven中profile

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

PropertySource

key-value對,API如下所示:

Java程式碼  收藏程式碼
  1. public String getName()  //屬性源的名字  
  2. public T getSource()        //屬性源(比如來自Map,那就是一個Map物件)  
  3. public boolean containsProperty(String name)  //是否包含某個屬性
      
  4. public abstract Object getProperty(String name)   //得到屬性名對應的屬性值  

非常類似於Map;用例如下:

Java程式碼  收藏程式碼
  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.     ResourcePropertySource propertySource2 = new ResourcePropertySource("resource""classpath:resources.properties"); //name, location  
  8.     System.out.println(propertySource2.getProperty("encoding"));  
  9. }  

MapPropertySource的屬性來自於一個Map,而ResourcePropertySource的屬性來自於一個properties檔案,另外還有如PropertiesPropertySource,其屬性來自Properties,ServletContextPropertySource的屬性來自ServletContext上下文初始化引數等等,大家可以查詢PropertySource的繼承層次查詢相應實現。

Java程式碼  收藏程式碼
  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:

Java程式碼  收藏程式碼
  1. public interface PropertySources extends Iterable<PropertySource<?>> {  
  2.     boolean contains(String name); //是否包含某個name的PropertySource  
  3.     PropertySource<?> get(String name); //根據name找到PropertySource  
  4. }  

示例如下:

Java程式碼  收藏程式碼
  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.     for(PropertySource propertySource : propertySources) {  
  9.         System.out.println(propertySource.getProperty("encoding"));  
  10.     }  
  11. }  

預設提供了一個MutablePropertySources實現,我們可以呼叫addFirst新增到列表的開頭,addLast新增到末尾,另外可以通過addBefore(propertySourceName, propertySource)或addAfter(propertySourceName, propertySource)新增到某個propertySource前面/後面;最後大家可以通過iterator迭代它,然後按照順序獲取屬性。

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

PropertyResolver

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

Java程式碼  收藏程式碼
  1. public interface PropertyResolver {  
  2.     //是否包含某個屬性  
  3.     boolean containsProperty(String key);  
  4.         //獲取屬性值 如果找不到返回null   
  5.     String getProperty(String key);  
  6.         //獲取屬性值,如果找不到返回預設值        
  7.     String getProperty(String key, String defaultValue);  
  8.         //獲取指定型別的屬性值,找不到返回null  
  9.     <T> T getProperty(String key, Class<T> targetType);  
  10.         //獲取指定型別的屬性值,找不到返回預設值  
  11.     <T> T getProperty(String key, Class<T> targetType, T defaultValue);  
  12.          //獲取屬性值為某個Class型別,找不到返回null,如果型別不相容將丟擲ConversionException  
  13.     <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);  
  14.         //獲取屬性值,找不到丟擲異常IllegalStateException  
  15.     String getRequiredProperty(String key) throws IllegalStateException;  
  16.         //獲取指定型別的屬性值,找不到丟擲異常IllegalStateException         
  17.     <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;  
  18.         //替換文字中的佔位符(${key})到屬性值,找不到不解析  
  19.     String resolvePlaceholders(String text);  
  20.         //替換文字中的佔位符(${key})到屬性值,找不到丟擲異常IllegalArgumentException  
  21.     String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;  
  22. }  

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

Java程式碼  收藏程式碼
  1. @Test  
  2. public void test() throws Exception {  
  3.     //省略propertySources  
  4.     PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);  
  5.     System.out.println(propertyResolver.getProperty("encoding"));  
  6.     System.out.println(propertyResolver.getProperty("no""default"));  
  7.     System.out.println(propertyResolver.resolvePlaceholders("must be encoding ${encoding}"));  //輸出must be encoding gbk  
  8. }  

從如上示例可以看出其非常簡單。另外Environment也繼承了PropertyResolver。

Environment

 環境,比如JDK環境,Servlet環境,Spring環境等等;每個環境都有自己的配置資料,如System.getProperties()、System.getenv()等可以拿到JDK環境資料;ServletContext.getInitParameter()可以拿到Servlet環境配置資料等等;也就是說Spring抽象了一個Environment來表示環境配置。

Java程式碼  收藏程式碼
  1. public interface Environment extends PropertyResolver {//繼承PropertyResolver  
  2.         //得到當前明確啟用的剖面  
  3.     String[] getActiveProfiles();  
  4.         //得到預設啟用的剖面,而不是明確設定啟用的  
  5.     String[] getDefaultProfiles();  
  6.         //是否接受某些剖面  
  7.     boolean acceptsProfiles(String... profiles);  
  8. }  

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

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

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

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

除了這些,我們也可以根據需求定義自己的Environment。示例如下:

Java程式碼  收藏程式碼
  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中配置:

Java程式碼  收藏程式碼
  1. <context-param>  
  2.     <param-name>myConfig</param-name>  
  3.     <param-value>hello</param-value>  
  4. </context-param>  
  5. <servlet>  
  6.     <servlet-name>spring</servlet-name>  
  7.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  8.     <init-param>  
  9.         <param-name>contextConfigLocation</param-name>  
  10.         <param-value>classpath:spring-mvc.xml</param-value>  
  11.     </init-param>  
  12. </servlet>  

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

然後在程式中通過如下程式碼注入Environment: 

Java程式碼  收藏程式碼
  1. @Autowired  
  2. Environment env;  

另外也可以直接使用ApplicationContext.getEnvironment()獲取;接著就可以用如下程式碼獲取配置:

Java程式碼  收藏程式碼
  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傳入系統引數

Java程式碼  收藏程式碼
  1. -Dspring.profiles.active=dev  

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

Java程式碼  收藏程式碼
  1. <context-param>  
  2.     <param-name>spring.profiles.active</param-name>  
  3.     <param-value>dev</param-value>  
  4. </context-param>  

三 、通過自定義新增PropertySource

Java程式碼  收藏程式碼
  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

Java程式碼  收藏程式碼
  1. env.setActiveProfiles("dev""test");  

以上方式都可以設定多個profile,多個之間通過如逗號/分號等分隔。  

接著我們就可以通過如下API判斷是否啟用相應的Profile了: 

Java程式碼  收藏程式碼
  1. if(env.acceptsProfiles("dev""test"))) {  
  2.     //do something  
  3. }  
它們之間是或的關係;即找到一個即可;如果有人想不匹配某個profile執行某些事情,可以通過如"!dev"  即沒有dev啟用時返回true。

當然這種方式還不是太友好,還需要我們手工程式設計使用,稍候會介紹如何更好的使用它們。

<context:property-placeholder/>

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

Java程式碼  收藏程式碼
  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.         />