通常在spring應用中,配置中的properties文件,都是打包在war包里的,部署規模較小,只有幾台服務器時,這樣並沒有什么大問題。如果服務器多了,特別是集群部署時,如果要修改某一項配置,得重新打包、部署,一台台機器改過去,十分麻煩。
看了Spring-Cloud項目,深受啟發,Spring-Cloud把配置文件放在遠程的git或svn這類雲平台之上,所有應用啟動時從雲上獲取配置,配置需要修改時,直接修改git上的配置即可,十分方便,但是這個項目並不簡單,新概念太多,需要一定時間熟悉。
借鑒一下spring-cloud的理念,我們可以把properties文件放在局域網的網絡位置上,啟動時遠程加載即可,核心實現類:
package org.demo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.StringUtils; import java.util.Properties; /** * load remote properties */ public class RemoteProperties implements InitializingBean, FactoryBean<Properties> { Logger logger = LogManager.getLogger(); private String url = null; private Properties properties = new Properties(); @Override public Properties getObject() throws Exception { return properties; } @Override public Class<?> getObjectType() { return properties.getClass(); } @Override public boolean isSingleton() { return true; } @Override public void afterPropertiesSet() throws Exception { loadProperty(); } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } private void loadProperty() { if (StringUtils.isEmpty(url)) return; logger.debug("loading remote properties:" + url); String content = HttpUtil.get(url); logger.debug("remote properties conent:" + content); String[] lines = content.replaceAll("\r", "").split("\n"); for (String line : lines) { if (!StringUtils.isEmpty(line)) { String[] arr = line.split("="); properties.put(arr[0].trim(), arr[1].trim()); } } } }
代碼不復雜,增加了一個url屬性,用來獲取遠程屬性文件的位置,然后在loadProperty里,發起http的get請求,把屬性文件的內容拿回來,存儲到本地properties變量中。使用時,配置里這么寫:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 7 <bean id="propertyPlaceholderConfigurer" 8 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 9 10 <!-- during developing, you can uncomment this to load local properties --> 11 <!-- <property name="location" value="application.properties"/> --> 12 13 <property name="properties"> 14 <bean id="remoteProperties" class="org.demo.RemoteProperties" 15 p:url="http://172.21.12*.**/config/dev/application.properties"/> 16 </property> 17 </bean> 18 19 <bean class="org.demo.Foo" p:name="${name}"></bean> 20 21 </beans>
注意13-16行,這里指定遠程屬性的位置,在開發階段,為了方便調試,可以把上面注釋的部分去掉,這樣相當於直接使用本地properties文件。
我在淘寶Code,上傳了一個示例程序spring-load-remote-config,方便大家參考。
當然,其實從文件路徑或http網址遠程屬性文件,Spring本身就支持的,配置示例如下:
1 <bean id="propertiesFactoryBean1" 2 class="org.springframework.beans.factory.config.PropertiesFactoryBean"> 3 <property name="location" 4 value="http://172.21.*.*/config/dev/application.properties" /> 5 </bean> 6 7 <bean id="propertiesFactoryBean2" 8 class="org.springframework.beans.factory.config.PropertiesFactoryBean"> 9 <property name="location" 10 value="file:D:\test\resources\application.properties" /> 11 </bean>
既然Spring已經支持了,還研究這個干嘛?
有些要求更高的場景,比如配置放在redis緩存里,或數據庫里,或者存儲在zookeeper節點上,或者屬性的值必須加密存儲,這些spring默認的實現是不夠的,明白上面的原理后,只要把loadProperty()方法的實現按需要修改即可。
注:如果把遠程屬性文件加載回來以后,還要做些后續處理,比如解密處理,RemoteProperties類的isSingleton()方法記得要返回false,否則之前的屬性值會因為單例模式而緩存,始終返回的是解密前的原始值。
更進一步探討:如果在遠程服務器,寫一個小程序監聽配置文件變化,然后結合ZooKeeper的訂閱、通知機制,子應用監聽到配置變化時,調用ApplicationContext.refresh()方法,刷新上下文環境,理論上講,應用連重啟都不需要。