天天看點

apache commons元件學習系列之configuration庫

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配置。