前言
Java应用中,配置文件十分常见。通常,我们使用配置文件对一些需要跟据现场实际运行环境进行配置的参数进行设置。
例如,数据库连接配置,服务器ip和端口,线程池大小,缓存大小等。
常见的配置文件格式有 .properties,.xml,.yml , .yaml
各种格式配置文件也具有各自的读取类库支持:
properties通过Java原生的Property 类就可支持
xml的解析类库有dom4j,Jdom等
yml的解析类库最常用的为snakeyml
配置文件反序列化的基本原理
在应用中,我们通过创建和配置文件属性一一映射的实体类,便于在程序中直接使用相关配置文件。
而解析配置文件,反序列化为Java对象的原理大同小异 ——
1. 读取磁盘配置文件
2. 根据对应格式,解析出属性名和对应的值,并缓存与Map结构中
3. 根据配置的类名,通过class对象反射getInstan生成对象
4. 获取对象的全部set方法,获取方法参数列表和参数名称。
5. 通过map 匹配出参数值和类型,有必要时进行类型转换
6. 调用invoke方法,完成设置
7. 返回对象
(以上仅考虑基本类型的配置,若配置文件支持对象的依赖注入,以上逻辑要更加复杂。类似spring的DI原理)
代码实现
根据上面的原理,可以看出,代码主要的原理是利用反射进行实现。逻辑繁琐的地方在于维护属性和值的关系,确定属性和类型的关系,进行类型转换。
考虑到目前配置文件在自己应用中的使用,仅需要解析一些基本类型,所以没有处理复杂的DI部分(只是自己简单写实现,想要真正全部稳定实现很复杂,且效率不如现有类库,所以,不要重复造轮子)
代码如下:
配置文件db.properties
driver = com.mark.test
url = 127.0.0.1:3306/test
userName = irving
password = test
num = 100
lnum = 9999999
映射实体类
/**
* 数据库配置
* @author irving
* @since 2017年7月22日 下午12:34:50
* @version MARK 0.0.1
*/
public class DBconfig extends BaseConfig{
private static final long serialVersionUID = 6044128461678569139L;
private String driver;
private String url;
private String userName;
private String password;
private int num;
private long lnum;
public String getDriver() {
return driver;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public long getLnum() {
return lnum;
}
public void setLnum(long lnum) {
this.lnum = lnum;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
工具类
/**
* 解析自己properties配置文件的工具类
* @author irving
* @since 2017年7月22日 下午12:35:33
* @version MARK 0.0.1
*/
public class PropertyParser {
private static final String SETPREFIX = "set";//set方法名前缀
public static <T> T parseProperties(String fileName, Class<T> type){
T obj = null;
try {
/**
* 通过Java原生properties读取属性
*/
InputStream ins = ClassLoader.getSystemResourceAsStream(fileName);
Properties properties = new Properties();
properties.load(ins);
// 实例化对象
obj = type.newInstance();
//维护属性名和属性对象的关系
Map<String, Field> fieldMap = new HashMap<String, Field>();
for(Field field:type.getDeclaredFields()){
String fieldName = field.getName();
String methodName = SETPREFIX + firstToUperCase(fieldName); //拼凑名称时首字母大写
fieldMap.put(methodName, field);
}
//遍历对象定义的所有方法,通过方法名获取出filed,进而确定类型,并设置对应值
for(Method method: type.getDeclaredMethods()){
Field field = fieldMap.get(method.getName());
if(null != field){
String paramTypeName = method.getParameterTypes()[0].getName();
switch (ClassType.typeOf(paramTypeName)) {
case INTEGER:
method.invoke(obj, Integer.parseInt(properties.getProperty(field.getName())));
break;
case LONG:
method.invoke(obj, Long.parseLong(properties.getProperty(field.getName())));
break;
default:
method.invoke(obj, properties.getProperty(field.getName())); //invoke 调用set方法
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
/**
* 首字母大写
* @author irving
* @since 2017年7月22日 上午11:42:30
* @version MARK 0.0.1
*/
private static String firstToUperCase(String str){
// str = str.substring(0, 1).toUpperCase()+str.substring(1);
// return str;
//直接通过数组char数组首字母asii转换,效率要高很多
char[] carry = str.toCharArray();
carry[0] -= 32;
return String.valueOf(carry);
}
public static void main(String[] args) {
DBconfig config = parseProperties("db.properties", DBconfig.class);
System.out.println(JsonConvertUtil.serializeObj(config));
}
}
总结
简单实现了一个自己解析和反序列化实体类的工具类,重点在于原理的理解。因此对一些实体类和配置文件的书写都很不规范。
此外,考虑效率原因,这个配置类解析类仅用于一些简单配置场景的读取,复杂情况。还是优先考虑使用yml这类效率高且支持复杂类型注入的类库