假设我们有一个配置文件config.properties,取自APDPlat的主配置文件:
#主配置文件 #是否启用WEB目录文件增加和删除监控 watch.directory.enable=true #用户密码安全策略 user.password.strategy=passwordLengthStrategy;passwordComplexityStrategy #如果启用数据库配置,则数据库中的配置信息有最高优先级,会覆盖配置文件的配置信息 config.db.enable=true #用配置文件中的信息强行覆盖数据库中的配置信息 config.db.override=false #使用哪一个模块,数据库配置中会用到此变量 module.short.name=apdplat
我们怎么写程序解析呢?很多人会告诉你使用java.util.Properties:
首先,准备容器:Properties props = new Properties();
其次,准备资源:ClassPathResource cr = new ClassPathResource("/org/apdplat/config.properties");
再次,加载资源:props.load(cr.getInputStream());
最后,使用配置:String value = props.getProperty(“watch.directory.enable”);
但是这种方式真的好吗?上面的步骤中,如果配置项包含中文,我们在加载资源之前还要执行一个步骤,执行命令:
native2ascii config.properties
用命令生成的结果来替换原来的配置:
#\u4e3b\u914d\u7f6e\u6587\u4ef6 #\u662f\u5426\u542f\u7528WEB\u76ee\u5f55\u6587\u4ef6\u589e\u52a0\u548c\u5220\u9664\u76d1\u63a7 watch.directory.enable=true #\u7528\u6237\u5bc6\u7801\u5b89\u5168\u7b56\u7565 user.password.strategy=passwordLengthStrategy;passwordComplexityStrategy #\u5982\u679c\u542f\u7528\u6570\u636e\u5e93\u914d\u7f6e\uff0c\u5219\u6570\u636e\u5e93\u4e2d\u7684\u914d\u7f6e\u4fe1\u606f\u6709\u6700\u9ad8\u4f18\u5148\u7ea7\uff0c\u4f1a\u8986\u76d6\u914d\u7f6e\u6587\u4ef6\u7684\u914d\u7f6e\u4fe1\u606f config.db.enable=true #\u7528\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684\u4fe1\u606f\u5f3a\u884c\u8986\u76d6\u6570\u636e\u5e93\u4e2d\u7684\u914d\u7f6e\u4fe1\u606f config.db.override=false #\u4f7f\u7528\u54ea\u4e00\u4e2a\u6a21\u5757\uff0c\u6570\u636e\u5e93\u914d\u7f6e\u4e2d\u4f1a\u7528\u5230\u6b64\u53d8\u91cf module.short.name=apdplat
可以看到,我们所有的中文注释都被转码了,我们人可看不懂啥意思啊,那怎么还原呢?看如下命令:
native2ascii config.properties | native2ascii -reverse
通过这个命令的输出你就知道,可以使用native2ascii -reverse命令来还原,但是,配置文件的好处就在于方便修改配置,现在被转换成这样,是不是很糟糕呢?肯定是了,那么怎么办呢?我们可以先还原原来的文本,然后把文本文件保存为utf-8格式,接着改变加载资源的方式,指定文件编码:
props.load(new InputStreamReader(cr.getInputStream(), "utf-8"));
这样就解决了。
更进一步,如果我们不用JDK内置的Properties,要自己实现,我们该如何编写配置文件解析程序呢?
我们使用UTF-8编码的普通文本文件,不用转码,在内存中使用HashMap来保存配置信息,如下代码所示,来自APDPlat的PropertyHolder类:
package org.apdplat.module.system.service;
import org.apdplat.platform.log.APDPlatLogger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apdplat.platform.log.APDPlatLoggerFactory;
import org.springframework.core.io.ClassPathResource;
/**
* 系统配置
* @author 杨尚川
*/
public class PropertyHolder {
private static final APDPlatLogger LOG = APDPlatLoggerFactory.getAPDPlatLogger(PropertyHolder.class);
private static final Map<String, String> PROPERTIES = new HashMap<>();
static {
init();
}
public static Map<String, String> getProperties() {
return PROPERTIES;
}
private static void load(InputStream inputStream, Map<String, String> map){
try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"))){
String line;
while((line = reader.readLine()) != null){
line = line.trim();
if("".equals(line) || line.startsWith("#")){
continue;
}
int index = line.indexOf("=");
if(index==-1){
LOG.error("错误的配置:"+line);
continue;
}
if(index>0 && line.length()>index+1) {
String key = line.substring(0, index).trim();
String value = line.substring(index + 1, line.length()).trim();
map.put(key, value);
}else{
LOG.error("错误的配置:"+line);
}
}
} catch (IOException ex) {
LOG.error("配置文件加载失败:" + ex.getMessage());
throw new RuntimeException(ex);
}
}
/**
* 本方法中的日志只能输出中文,因为APDPlatLoggerImpl中默认指定输出中文
* 只有配置项加载完毕,调用了指定日志输出语言方法LOG.setLocale(getLogLanguage())
* 之后,配置的日志输出语言才会生效
*/
private static void init() {
String systemConfig="/org/apdplat/config.properties";
String localConfig="/config.local.properties";
String dbConfig="/org/apdplat/db.properties";
String localDBConfig="/db.local.properties";
ClassPathResource cr = null;
try{
cr = new ClassPathResource(systemConfig);
load(cr.getInputStream(), PROPERTIES);
LOG.info("装入主配置文件:"+systemConfig);
}catch(Exception e){
LOG.info("装入主配置文件"+systemConfig+"失败!", e);
}
try{
cr = new ClassPathResource(localConfig);
load(cr.getInputStream(), PROPERTIES);
LOG.info("装入自定义主配置文件:"+localConfig);
}catch(Exception e){
LOG.info("装入自定义主配置文件"+localConfig+"失败!", e);
}
try{
cr = new ClassPathResource(dbConfig);
load(cr.getInputStream(), PROPERTIES);
LOG.info("装入数据库配置文件:"+dbConfig);
LOG.info("Database profile is loaded:"+dbConfig);
}catch(Exception e){
LOG.info("装入数据库配置文件"+dbConfig+"失败!", e);
}
try{
cr = new ClassPathResource(localDBConfig);
load(cr.getInputStream(), PROPERTIES);
LOG.info("装入自定义数据库配置文件:"+localDBConfig);
}catch(Exception e){
LOG.info("装入自定义数据库配置文件"+localDBConfig+"失败!",e);
}
String extendPropertyFiles = PROPERTIES.get("extend.property.files");
if(extendPropertyFiles!=null && !"".equals(extendPropertyFiles.trim())){
String[] files=extendPropertyFiles.trim().split(",");
for(String file : files){
try{
cr = new ClassPathResource(file);
load(cr.getInputStream(), PROPERTIES);
LOG.info("装入扩展配置文件:"+file);
}catch(Exception e){
LOG.info("装入扩展配置文件"+file+"失败!",e);
}
}
}
LOG.info("系统配置属性装载完毕");
LOG.info("******************属性列表***************************");
PROPERTIES.keySet().forEach(propertyName -> {
LOG.info(" " + propertyName + " = " + PROPERTIES.get(propertyName));
});
LOG.info("***********************************************************");
//指定日志输出语言
LOG.setLocale(getLogLanguage());
}
/**
* 日志使用什么语言输出
* @return
*/
public static Locale getLogLanguage(){
String language = getProperty("log.locale.language");
return Locale.forLanguageTag(language);
}
public static boolean getBooleanProperty(String name) {
String value = PROPERTIES.get(name);
return "true".equals(value);
}
public static int getIntProperty(String name) {
String value = PROPERTIES.get(name);
return Integer.parseInt(value);
}
public static String getProperty(String name) {
String value = PROPERTIES.get(name);
return value;
}
public static void setProperty(String name, String value) {
PROPERTIES.put(name, value);
}
}