commons configuration庫允許application從不同形式的配置檔案中讀寫配置資料。
它不僅可以從properties,xml,ini檔案讀取配置,支援變量插值,
還可以從系統配置,Servlet參數中讀取配置。
更可以通過JDBC或者JNDI資料源讀寫資料庫中的配置。
也可以組合多種配置檔案同時使用。
同時支援檔案中的變量插值,自動類型轉換,還可支援jexl表達式語言等,同時你可以自由定制各種行為以滿足您的需求。
非常強大,使用也非常簡單。
注意本文基于的是Apache Commons Configuration元件1.10版本而言的。
Configuration 2.0版本還在開發中,沒有正式釋出。
Configuration 2.0 SVN Checkout位址:http://svn.apache.org/viewvc/commons/proper/configuration/trunk/
但是目前為止已經釋出了2.0-SANPSHOT版本,可惜不對外公布。我們可以通過以下連接配接擷取:
2.0-SNAPSHOT:https://repository.apache.org/content/repositories/snapshots/commons-configuration/commons-configuration/2.0-SNAPSHOT/
由于2.0-SNAPSHOT接口很不完整,是以千萬别看完此文後去嘗試2.0版本。
以下均為基于1.X版本而言。2.0版本中的在此處介紹的有些特性還沒有完全實作。
注意:任何針對1.X版本中的寫操作都是非線程安全的。
一基礎介紹:
例如:
Double double = config.getDouble("number");
Integer integer = config.getInteger("number");
1.元件支援的配置檔案形式有如下幾種:
- Properties files
- XML documents
- Windows INI files
- Property list files (plist)
- JNDI
- JDBC Datasource
- System properties
- Applet parameters
- Servlet parameters
當然你也可以通過DefaultConfigurationBuilder(新版API不再推薦使用ConfigurationFactory)和CompositeCOnfiguration組合使用多種形式的配置。(如,system配置和xml檔案配置一起使用)
當然如果你有特殊需要可以繼承AbstractConfiguration or AbstractFileConfiguration來讀取你自己格式的配置檔案,
以上所有的配置檔案我們都可以一緻地通過Configuration接口來進行操作。
2.下面我們說一下混合多重配置形式的方式:
第一種方式:
需要的類:CompositeConfiguration,
CompositeConfiguration config = new CompositeConfiguration();
config.addConfiguration(new SystemConfiguration());
config.addConfiguration(new PropertiesConfiguration("application.properties"))
第二種方式:使用DefaultConfigurationBuilder(ConfigurationFactory現在已不推薦)
DefaultConfiguration主要用于複雜項目中多個配置檔案的讀取。注意:DefaultConfiguration隻能使用多配置檔案的配置檔案來初始化。
是以在使用時一定要提供一個配置檔案的配置檔案。對,你沒看錯。
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder("config.xml");
Configuration config = builder.getConfiguration();
也可以通過如下形式:
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
builder.setFile(new File("config.xml"));
Configuration config = builder.getConfiguration(true);
其中config.xml是我們的配置檔案的配置檔案的路徑,這個檔案指定了需要混合的檔案。内容舉例:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<configuration>
<system/> //指定混合系統配置屬性。
<properties fileName="application.properties"/> //指定需要混合的配置檔案為apllication.properties
<xml fileName="gui.xml"/>
//該配置檔案可用的子元素有
//xml,properties,jndi,plist,system,configuration,ini,env,
</configuration>
那這些屬性是如何尋找的呢,即尋找這些屬性的順序是什麼?要是要沖突的屬性名,又是如何處理的呢?
處理方式是這樣的:從最先聲明的屬性配置開始查找,如果找到某個屬性的值就傳回,否則就去下一個配置中尋找。,這樣我們就知道如果存在沖突,也不會将後面的配置覆寫前面的。
3.前面我們說過可以使用Configuration接口完成一緻性地操作。
configuration接口允許我們通過屬性名查找,也可以在查找時選擇性的提供預設值defaultValue,同時Configuration會在背景幫助我們自動完成類型轉換。
configuration支援的自動類型轉換:
- BigDecimal
- BigInteger
- boolean
- byte
- double
- float
- int
- long
- short
- String
這些通過getXXX()方法來擷取,同時如果一個屬性名擁有多個值,我們可以通過getList()或者,getArray()擷取多個值清單。
同時我們可以對配置檔案中的屬性進行添加,讀,寫操作等
通過addProperty()添加屬性,如果存在相同的屬性名,則給這個屬性添加一個值,而非覆寫。
clearProperty則是删除一個屬性
clear()删除所有屬性。
setProperty設定屬性。覆寫屬性值。
containsKey(String key),檢查屬性是否存在。
4.線程安全問題:
Configuration的實作并沒有提供線程安全機制。是以隻能確定在多線程環境下的隻讀安全性,
如果需要修改屬性,則需要進行同步。
5.處理屬性不存在的情況:
如果所通路的屬性并不存在,元件可能會有以下兩種處理方式:
對于傳回值是Object類型的,傳回null,如傳回值為String類型
對于傳回值時基本類型的,抛出NoSunchElementException異常。如傳回值為long類型
對于擁有多個值的屬性通過getList()或getArray擷取時,它隻是傳回一個empty的list。
是以我們最好最擷取屬性時提供一個預設值。
二、關于XML屬性配置的說明:
1.體系:
HiberarchicalConfiguration代為層次配置的抽象
XMLConfiguration作為其實作。
2.配置執行個體與說明:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<gui-definition>
<colors>
<background>#808080</background>
<text>#000000</text>
<header>#008000</header>
<link normal="#000080" visited="#800080"/>
<default>${colors.header}</default>
</colors>
<rowsPerPage>15</rowsPerPage>
<buttons>
<name>OK,Cancel,Help</name>
</buttons>
<numberFormat pattern="###\,###.##"/>
</gui-definition>
try
{
XMLConfiguration config = new XMLConfiguration("tables.xml");
// do something with config
}
catch(ConfigurationException cex)
{
}
String backColor = config.getString("colors.background");
String textColor = config.getString("colors.text");
String linkNormal = config.getString("colors.link[@normal]");
String defColor = config.getString("colors.default");
int rowsPerPage = config.getInt("rowsPerPage");
List<Object> buttons = config.getList("buttons.name");
說明:
1.root元素:在尋找屬性key時是直接被忽略的,如<gui-definition>這個root element就直接被忽略
2.關于屬性鍵key:我可以通過colors.background形式層次式以點分割尋找屬性。
3.可以通過字元分割形式預設為comma,即逗号的形式給一個xml元素配置多個值,如<name>元素。這就會導緻name屬性key有多個value。
是以name屬性的擷取需要通過getList()來進行。
如果值确實有包含comma的必要,那就使用\轉義.如果你讨厭預設的comma來分割值,你可以通過setDefaultDelimiter()(1.10API中沒有這一項)來設定預設的分割符
4.由于dot點在key尋找的過程中扮演特殊地位,是以建議不要在元素名稱中使用點如<name.xby>這是不推薦的
5配置檔案的尋找機制:
- 路徑為url形式,則通過URL來加載load;
- 否則路徑為絕對路徑形式,通過絕對路徑加載;
- 否則路徑為設定了base path之後的相對路徑形式,則加載它;
- 否則檔案為普通檔案名,如果配置檔案在使用者的home目錄,加載它;
- 否則配置檔案名文普通檔案名形式,在classpath中,加載它。
- 否則抛出COnfigurationException異常。
6..configuration元件運作配置檔案中使用變量插值varibal interpolator。如以上的${colors.home}:
變量插值的規則:
${color.home}這沒有字首,代表在本配置檔案内也就是這個xml檔案内尋找<color>下<home>這個元素的值作為變量值。
第二種形式為有字首${prefix:var}
元件内置了三個字首:sys,const,env:
sys-用于通路系統的相關變量.
env-用于通路系統特定的環境變量
const-用于通路classpath路徑下的類中的static final field.
舉例:
${sys:user.home}/settings.xml
${const:java.awt.event.KeyEvent.VK_CANCEL}
${env:JAVA_HOME}
如果你需要定制自己的變量插值字首以滿足自定制要求:你需要以下工作:
a.引入apache commons lang元件并.繼承org.apache.commons.lang3.text.StrLookup<V>,覆寫其中的lookup方法。
這個lookup方法将變量名作為參數傳入,并傳回變量值。
import org.apache.commons.lang.text.StrLookup;
public class EchoLookup extends StrLookup
{
public String lookup(String varName)
{
return "Value of variable " + varName;
}
}
b.完成這個類之後,需要将其注冊到:org.apache.commons.configuration.interpol.ConfigurationInterpolator.
這樣我們才能使用該類來充當變量插值字首,才能在應用加載配置檔案時正确解析。
// Place this code somewhere in an initialization section of your application
ConfigurationInterpolator.registerGlobalLookup("echo", new EchoLookup());
這段注冊代碼必須要在應用初始化時,或者調用建立Configuration對象之前完成,否則将不會起作用。
這樣我們就可以通過以下形式使用改字首${echo:xby}
三、關于屬性操作
1.修改與儲存:屬性修改之後需要寫入配置檔案。寫入分為手動寫入與自動寫入。
手動儲存:
PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
config.setProperty("colors.background", "#000000");
config.save();
自動儲存:
PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
config.setAutoSave(true);
config.setProperty("colors.background", "#000000"); // the configuration is saved after this call
2.自動重新加載Reload:配置檔案修改之後需要重新加載Reload(适用于1.X,2.0的源碼中已經看不到這個了)
PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
config.setReloadingStrategy(new FileChangedReloadingStrategy());
3.路徑分割操作:
<dir > C:\\Temp\\</div>
四、CompositeConfiguration組合配置說明:
Configuration defaults = new PropertiesConfiguration(fileToDefaults);
Configuration otherProperties = new PropertiesConfiguration(fileToOtherProperties);
CompositeConfiguration cc = new CompositeConfiguration();
cc.addConfiguration(otherProperties);
cc.addConfiguration(fileToDefaults);
儲存變化:
PropertiesConfiguration saveConfiguration = new PropertiesConfiguration(fileToSaveChangesIn);
Configuration cc = new CompositeConfiguration(saveConfiguration);
cc.setProperty("newProperty","new value");
saveConfiguration.save();
舉例:使用資料庫通過資料源中提取配置:
Configuration changes = myCompositeConfiguration.getInMemoryConfiguration();
DatabaseConfiguration config = new DatabaseConfiguration(datasource, "tablename", "key", "value");
for (Iterator<String> i = changes.getKeys(); i.hasNext()){
String key = i.next();
Object value = changes.get(key);
config.setProperty(key,value);
}
五、使用資料源DataSource配置。
主要類DatabaseConfiguration.
DatabaseConfiguration(DataSource datasource, String table, String keyColumn, String valueColumn)
六、配置檔案讀寫的線程安全性支援--2.0版本的新特性,
注意:任何針對1.X版本中的寫操作都是線程不安全的。
2.0版本新增了線程安全性支援。它是通過内置的Synchronizer和其實作如ReentrantReadWriteLock實作的。
設定Synchronizer實作,
config.setSynchronizer(new ReadWriteSynchronizer());
之後我們便可以使用簡單的同步了,這對大部分情況下時沒有問題的。但是,
為什麼說是簡單的同步呢?因為這是會對單個的讀寫操作進行同步,如果需要複雜得原子操作,則這是不能滿足需求的。原因可以從源碼中看出。
以下去自項目源碼:
public final void addProperty(String key, Object value)
{
beginWrite(false);
try
{
fireEvent(EVENT_ADD_PROPERTY, key, value, true);
addPropertyInternal(key, value);
fireEvent(EVENT_ADD_PROPERTY, key, value, false);
}
finally
{
endWrite();
}
}
public final Object getProperty(String key)
{
beginRead(false);
try
{
return getPropertyInternal(key);
}
finally
{
endRead();
}
}
protected void beginWrite(boolean optimize)
{
getSynchronizer().beginWrite();
}
以上代碼分析取自該項目源代碼。
我們可以看到,對于我們的讀寫操作包括對List的擷取及讀取等操作均可以進行可重入讀寫鎖的同步,進而達到線程安全
但是如果我們需要一次性寫入或添加多個屬性呢?
這是我們需要進行下一步操作的情形,使用lock().unlock()對範圍進行加鎖、
config.lock(LockMode.WRITE);
try
{
config.addProperty("prop1", "value1");
...
config.addProperty("prop_n", "value_n");
}
finally
{
config.unlock(LockMode.WRITE);
}
config.lock(LockMode.READ);
try
{
// read access to syncSupport
}
finally
{
syncSupport.unlock(LockMode.READ);
}
3.記住,除非你确實不需要對配置檔案進行更改,你或許隻是對配置檔案進行讀取,否則你必須要掌握這個同步操作的用法,已達到線程安全性。
六、配置檔案、Properties對象之間的轉換,複制等
複制使用copy();
properties轉xml:
// Create a flat configuration
PropertiesConfiguration flatConfig = new PropertiesConfiguration();
flatConfig.load(...);
HierarchicalConfiguration hc =
ConfigurationUtils.convertToHierarchical(flatConfig);
properties檔案轉Properties對象
Properties processConfiguration(Properties props)
{
// Create a configuration for the properties for easy access
Configuration config = ConfigurationConverter.getConfiguration(props);
// Now use the Configuration API for manipulating the configuration data
...
// Return a Properties object with the results
return ConfigurationConverter.getProperties(config);
}
其他特性:
1.表達式語言支援,jexl
2.自定義類型轉換。
3.事件與監聽機制
4.bean配置。