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.配置实例与说明:



  
    #808080
    #000000
    
#008000
${colors.header}
15 OK,Cancel,Help
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 buttons = config.getList("buttons.name");
 
  

说明:

1.root元素:在寻找属性key时是直接被忽略的,如这个root element就直接被忽略

2.关于属性键key:我可以通过colors.background形式层次式以点分割寻找属性。

3.可以通过字符分割形式默认为comma,即逗号的形式给一个xml元素配置多个值,如元素。这就会导致name属性key有多个value。

所以name属性的获取需要通过getList()来进行。

 如果值确实有包含comma的必要,那就使用\转义.如果你讨厌默认的comma来分割值,你可以通过setDefaultDelimiter()(1.10API中没有这一项)来设置默认的分割符

4.由于dot点在key寻找的过程中扮演特殊地位,所以建议不要在元素名称中使用点如这是不推荐的

5配置文件的寻找机制:

  •    路径为url形式,则通过URL来加载load;
  •     否则路径为绝对路径形式,通过绝对路径加载;
  •    否则路径为设置了base path之后的相对路径形式,则加载它;
  •   否则文件为普通文件名,如果配置文件在用户的home目录,加载它;
  •   否则配置文件名文普通文件名形式,在classpath中,加载它。
  •  否则抛出COnfigurationException异常。

6..configuration组件运行配置文件中使用变量插值varibal interpolator。如以上的${colors.home}:

变量插值的规则:

${color.home}这没有前缀,代表在本配置文件内也就是这个xml文件内寻找这个元素的值作为变量值。

第二种形式为有前缀${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,覆盖其中的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\\

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

你可能感兴趣的:(JAVA)