构建自己的Properties

本文介绍了一种名为PropertyFileCache的实现方案,该方案能够实现在不重启应用程序的情况下更新属性文件。通过缓存和定时检查文件修改时间戳的方式,确保了属性文件更新后应用程序能即时响应。
开发到了一定时间,就会有很多的定式思维阻碍我们。 比如问到XML配置和Properties配置有什么区别,往往想都不想就回答: 更改Properties需要重新启动Server,而更改XML Schema则不需要重新启动Server就会生效。
虽然上面的说法是错误的 ,但是还是值得我们去思考为什么。
另一方面,我们要构建7*24小时运转的System,不能随意关闭 /启动 APP Server. 但是又要保证让我们更改系统配置,我们的Application又能立即感知。基于这方面的需求。 我们做了PropertyFileCache。 基本原理是模仿Properties,每次访问getProperty时,去检查文件修改的时间戳。如果时间为后来的,则代表文件已经修改,则需要作类似 Properties.load()的动作.
然后重新取得新的修改后的值。否则,采用Cache中的值。
下面是直接的代码.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

//import org.apache.log4j.Logger;


//
// PropertyFileCache() :
// This class reads a property file [full file name specified], then, put each line
// with a key and value property to check. For example, if the file contains IP address...
//
// 127.0.0.1=ok
// 202.3.8.4=blah
//
// The above example will put the above IP addresses as key, and "ok" and "blah" as its
// associated values in a cached array. If the property file has changed, it will read it again.
// If the property file has not changed, then, upon a request, it will simply
// pull elements from cache/memory to check.
//
// This class also allows multiple instance of file to be cached.
//
public class PropertyFileCache {
private static final String ENCODING = "UTF-8"; // encoding type...
private static final String PARAMETER_DELIMITER = "="; // equate character for key and value
private static final String COMMENT_DELIMITER = "#"; //comment delimiter inside the property file
private static final int PRINT_ELEMENT_LIMIT = 50; // if more than X, do not print all read elements...

// private static Logger sysLog = Logger.getLogger(PropertyFileCache.class);

protected static PropertyFileCache instance; // singelton instance

protected Hashtable<String, FileInfo> spfCache; // this stores different property file...

protected PropertyFileCache() {
spfCache = new Hashtable<String, FileInfo>();

}

//
// getInstance(): get this instance - a singleton
//
public static PropertyFileCache getInstance() {
if ( instance == null ) {
instance = new PropertyFileCache();
}
return instance;
}

//
// getAllKeys() : return an enumeration of all keys
//
public Enumeration <String> getAllKeys(String filename) {
Enumeration<String> allkeys = null;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
allkeys = curFileInfo.cachedTokens.keys();
}
return allkeys;
}

//
// keySet() : return a set for all keys available
//
public Set<String> keySet(String filename) {
Set<String> keyset = new HashSet<String>(); // JP, Aug 16, 2006, initialize it to an empty Set
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
keyset = curFileInfo.cachedTokens.keySet();
}
return keyset;
}


//
// getProperty(): get the who hash table for internal processing
//
private Hashtable<String,String> getProperty(String filename) {
Hashtable<String, String> retTable = null;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
retTable = curFileInfo.cachedTokens;
} else {
retTable = new Hashtable<String, String>(); // return empty vector
}
return retTable;
}
//
// get(): get the value based on the key and filename
//
public String get(String filename, String key) {
String retStr = null;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
retStr = curFileInfo.cachedTokens.get(key);
}
return retStr;
}

//
// getTimestamp() : return the timestamp of this file. this is used by external routine to explicit triggers a re-read
//
public long getTimestamp(String filename) {
long ts = 0;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null) {
checkConf(curFileInfo);
ts = curFileInfo.timestamp;
}
return ts;
}

//
// contains() : check if the item contains the token
//
public boolean contains(String filename, String token) {
boolean status = false;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
status = contains(curFileInfo.cachedTokens, token);
}
return status;
}

//
// containsKey() : check if key is there!
//
public boolean containsKey(String filename, String token) {
boolean status = false;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
status = containsKey(curFileInfo.cachedTokens, token);
}
return status;
}

//
// getSize() : return the number of items in the file
//
public int getSize(String filename) {
int size = 0;
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
checkConf(curFileInfo);
size = curFileInfo.cachedTokens.size();
}
return size;
}

//
// isUpdated() : check to see if time stamp has been updated. This check DOES NOT trigger a re-read!
//
public boolean isUpdated(String filename) {
boolean status = false;
if ( filename != null && filename.length() > 0 ) {
FileInfo curFileInfo = getFileInfo(filename);
if ( curFileInfo != null ) {
long newTimestamp = (new File(curFileInfo.filename)).lastModified();
// NOTE: if file does not exist, lastModified() will return '0', let
// it proceed to create a zero element HashTable
if ( newTimestamp > curFileInfo.timestamp || newTimestamp == 0) {
status = true; // file has been updated!
} // else - no need to do anything!
} else {
status = true; // signal update is required because file has not been read before!
}
} // else - if file name is invalid, there is nothing to be 'updated', return false...
return status;
}

private FileInfo getFileInfo(String filename) {
FileInfo curFileInfo = null;
if ( filename != null && filename.length() > 0 ) {
try {
if ( spfCache.containsKey(filename)) {
curFileInfo = spfCache.get(filename);
} else {
curFileInfo = new FileInfo(filename);
spfCache.put(filename, curFileInfo);
}
curFileInfo = spfCache.get(filename);
} catch (Exception e) {
// sysLog.error("Cannot read simple property file [" + filename + "]");
}
}
return curFileInfo;
}

private String get(Hashtable<String, String> vs, String key) {
return vs.get(key);
}

private boolean contains(Hashtable<String,String> vs, String token) {
return vs.contains(token);
}

private boolean containsKey(Hashtable<String, String> vs, String key) {
return vs.containsKey(key);
}

private void checkConf(FileInfo fi) {
String curDir = null;
try {
curDir = (new File(".")).getAbsolutePath();
// if 'fp' is null, meaning cannot read a file, will throw exception
long newTimestamp = (new File(fi.filename)).lastModified();

// NOTE: if file does not exist, lastModified() will return '0', let
// it proceed to create a zero element HashTable
if ( newTimestamp > fi.timestamp || newTimestamp == 0) {
parseProperties(fi); // similar to Properties.load(), but have to deal with UTF-8!
fi.timestamp = newTimestamp;
// sysLog.info("Parsed from DIR[" + curDir + "] + FILE["+ fi.filename + "] with timestamp =[" + fi.timestamp +"]");
}
} catch (Exception e) {
// sysLog.fatal("Cannot read file [" + fi.filename + "] from directory [" + curDir + "]..." + e.getMessage());
}
}

private void parseProperties(FileInfo fi) {
if (fi.filename != null && fi.filename.length() > 0 ) {
Hashtable <String, String> tokensFromFile = new Hashtable<String, String>();

try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(fi.filename), ENCODING));
String tmpValue; // extracted value
String tmpKey; // extracted key
String tmpLine; // the line

while ( (tmpLine = br.readLine()) != null ) {
//
// strip off any comment and trim it down
//
tmpLine = tmpLine.replaceAll(COMMENT_DELIMITER + ".*$", "").trim();
int sindex = tmpLine.indexOf(PARAMETER_DELIMITER);

if ( sindex != -1 ) {
tmpKey = tmpLine.substring(0, sindex).trim();
tmpValue = tmpLine.substring(sindex + 1).trim();

if ( /* tmpValue.length() > 0 && */ tmpKey.length() > 0 ) { // allow empty string to be a variable...
tokensFromFile.put(tmpKey, tmpValue);
} // else ignore the parameters
} // else ignore the parameters
}
br.close();
// sysLog.info("Property file [" + fi.filename + "] loaded with " + tokensFromFile.size() + " element(s).");
} catch (Exception e) {
// sysLog.error("Property file [" + fi.filename + "] cannot be loaded " + e.getMessage());
} finally {
if (tokensFromFile != null ) {
if ( fi.cachedTokens != null ) {
fi.cachedTokens.clear(); // remove old Vector
}
fi.cachedTokens = tokensFromFile; // use new table
if ( fi.cachedTokens.size() < PRINT_ELEMENT_LIMIT ) { // if there are too many, do not print...
// sysLog.debug("Property file containing elements...\n" + fi.cachedTokens.toString());
} // else - don't bother printing...
}
}
} else {
// sysLog.error("Property file [" + fi.filename + "] is not defined");
}
}


//
// CAUTION: Do not try to combine this with SimplePropertyFileCache.java. THEY ARE DIFFERENT!!!
//
final class FileInfo {
private FileInfo(String fn) {
filename = fn;
// no need to initialize timestamp and cachedTokens, it will be determined later
}
private String filename;
private long timestamp;
private Hashtable<String, String> cachedTokens;
}
}


使用的例子:
String value = PropertyFileCache.getInstance().get("./bin/servletmap/instr2url.properties", "key");


需要注意的是,它并不能[color=red]取代[/color]java.util.Properties[url]http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/util/Properties.html[/url],[color=brown]很多的特殊字符转义暂时还没有考虑[/color]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值