我们的代码为了重用或是项目为了更好地适应运营的变化,总免不了要使用配置文件,但有些项目需要24小时运营,不能为了修改配置而停机重启,所以配置文件的动态加载就显得较为重要,下面要介绍的就是commons项目的动态加载配置文件的机制,及项目中如何利用利用commons来实现配置的动态加载。
先介绍项目源码地址: http://git.oschina.net/rjzjh/commons,maven中央库引用:
<dependency>
<groupId>net.wicp.tams</groupId>
<artifactId>commons</artifactId>
<version>0.1.5</version>
</dependency>
commons包会读classpath根目录下的commonsUtil.properties配置文件,如果该目录下没有找到该文件,那么commons包会读取jar包中的commonsUtil.properties配置文件来做为该包的配置文件:
所有的配置,配置文件的热加载由net.wicp.tams.commons.Conf来完,Conf由TimerTask来完成Job工作,它会一分钟执行一次配置文件的比较工作,如果扫描的配置文件的修改时间大于已加载的配置文件的修改时间,则会启动各模块的的回调工作
if (curmodified > lastModified) {
lastModified = curmodified;
Properties oldProperties = (Properties) utilProperties.clone();// 旧配置属性
Properties newProperties = new Properties();
fileInputStream = new FileInputStream(file);
newProperties.load(fileInputStream);
// 重新装配新的属性
utilProperties.clear();
for (Object key : newProperties.keySet()) {
utilProperties.put(key, newProperties.get(key));
}
for (String moudle : reshBacks.keySet()) {
String[] propNames = props.get(moudle);
if (ArrayUtils.isEmpty(propNames)) {// 没有观察的属性名称不做调用
continue;
}
// 查找是否观察的属性有变化
boolean ischange = false;
for (String propName : propNames) {
if (propName.endsWith("%s")) {// 取多个属性值,如redisserver%s
String keyPre = propName.substring(0, propName.length() - 2);
Map<String, String> oldmap = CollectionUtil.getPropsByKeypre(oldProperties, keyPre);
Map<String, String> newmap = CollectionUtil.getPropsByKeypre(newProperties, keyPre);
for (String key : oldmap.keySet()) {
String oldValue = oldmap.get(key);
String newValue = newmap.get(key);
if (!StringUtil.hasNull(oldValue).equals(StringUtil.hasNull(newValue))) {
ischange = true;
break;
}
}
if (ischange) {
break;
}
} else {
String oldValue = oldProperties.getProperty(propName);
String newValue = newProperties.getProperty(propName);
if (!StringUtil.hasNull(oldValue).equals(StringUtil.hasNull(newValue))) {
ischange = true;
break;
}
}
}
if (ischange) {
try {
reshBacks.get(moudle).doReshConf(utilProperties);// 也是新的Properties
} catch (Exception e) {
logger.error("加载配置文件失败,回调模块[" + moudle + "]错误", e);
}
}
}
logger.info("成功刷新配置文件");
}
其中 reshBacks 就是各模块的回调函数,看RedisClient的工具类:
Conf.addCallBack("redis", new Callback() {
@Override
public void doReshConf(Properties newProperties) {
RedisClient.setInitPool(true);// Redis动态刷新
}
}, "redisserver%s");
redis指的是模块名,第二个参数是回调函数,第三个参数是指该模块所关心的配置项,如果commonsUtil.properties文件中以redisserver开始的属性项有改动,则会触发第二个参数的的回调对象,RedisClient.setInitPool(true);会重新跟据新的配置属性来初始化redis连接池。
commons包中所有的模块的属性动态加载都是用上述一样的机制,如果项目中也需要动态加载,可以在在classpath的目录下的commonsUtil.properties文件配置好自己的属性配置项,然后在适当的时间(如类加载的时候或是单例时对象实例化时候),通过Conf.addCallBack方法加入回调对象(实现net.wicp.tams.commons.Conf.Callback接口),就可以完成配置文件的热加载。如果没有要初始化或是要析构的东西可以不用加入回调对象,直接通过Conf.get方法拿到所配置的配置项值就好了。