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程式碼- public String getName() //屬性源的名字
- public T getSource() //屬性源(比如來自Map,那就是一個Map物件)
-
public boolean containsProperty(String name) //是否包含某個屬性
- public abstract Object getProperty(String name) //得到屬性名對應的屬性值
非常類似於Map;用例如下:
Java程式碼- @Test
- public void test() throws IOException {
- Map<String, Object> map = new HashMap<>();
- map.put("encoding", "gbk");
-
PropertySource propertySource1 = new MapPropertySource("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的繼承層次查詢相應實現。
Java程式碼- @Test
- 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:
Java程式碼- public interface PropertySources extends Iterable<PropertySource<?>> {
- boolean contains(String name); //是否包含某個name的PropertySource
- PropertySource<?> get(String name); //根據name找到PropertySource
- }
示例如下:
Java程式碼- @Test
- 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來解析屬性了。
PropertyResolver
屬性解析器,用來根據名字解析其值等。API如下所示:
Java程式碼- 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上我們已經看出解析器的作用了,具體功能就不要羅嗦了。示例如下:
Java程式碼- @Test
- 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。
Environment
環境,比如JDK環境,Servlet環境,Spring環境等等;每個環境都有自己的配置資料,如System.getProperties()、System.getenv()等可以拿到JDK環境資料;ServletContext.getInitParameter()可以拿到Servlet環境配置資料等等;也就是說Spring抽象了一個Environment來表示環境配置。
Java程式碼- 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。示例如下:
Java程式碼- @Test
- 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中配置:
Java程式碼- <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:
Java程式碼- @Autowired
- Environment env;
另外也可以直接使用ApplicationContext.getEnvironment()獲取;接著就可以用如下程式碼獲取配置:
Java程式碼- 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
profile,剖面,大體意思是:我們程式可能從某幾個剖面來執行應用,比如正式機環境、測試機環境、開發機環境等,每個剖面的配置可能不一樣(比如開發機可能使用本地的資料庫測試,正式機使用正式機的資料庫測試)等;因此呢,就需要根據不同的環境選擇不同的配置;如果用過maven,maven中就有profile的概念。
profile有兩種:
預設的:通過“spring.profiles.default”屬性獲取,如果沒有配置預設值是“default”
明確啟用的:通過“spring.profiles.active”獲取
查詢順序是:先進性明確啟用的匹配,如果沒有指定明確啟用的(即集合為空)就找預設的;配置屬性值從Environment讀取。
API請參考Environment部分。設定profile屬性,常見的有三種方式:
一、啟動Java應用時,通過-D傳入系統引數
Java程式碼- -Dspring.profiles.active=dev
二、如果是web環境,可以通過上下文初始化引數設定
Java程式碼- <context-param>
- <param-name>spring.profiles.active</param-name>
- <param-value>dev</param-value>
- </context-param>
三 、通過自定義新增PropertySource
Java程式碼- Map<String, Object> map = new HashMap<String, Object>();
- map.put("spring.profiles.active", "dev");
- MapPropertySource propertySource = new MapPropertySource("map", map);
- env.getPropertySources().addFirst(propertySource);
四、直接設定Profile
Java程式碼- env.setActiveProfiles("dev", "test");
以上方式都可以設定多個profile,多個之間通過如逗號/分號等分隔。
接著我們就可以通過如下API判斷是否啟用相應的Profile了:
Java程式碼- if(env.acceptsProfiles("dev", "test"))) {
- //do something
- }
當然這種方式還不是太友好,還需要我們手工程式設計使用,稍候會介紹如何更好的使用它們。
<context:property-placeholder/>
${key}佔位符屬性替換器,配置如下:
Java程式碼- <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="順序"
- />