背景:项目配置文件分散,有FTP、文件格式、数据库等。且单个任务配置都不一样。故有了统一配置文件的想法,由统一的工具类生成配置对象。
使用:dom4j,Java ioc
首先是XML文件的定义,懒得写schema,直接手写了。配置项由于很多,且分类明确,所以一开始就打算将其分为多个Java类配置。
1<?xml version="1.0" encoding="UTF-8"?>
2<Config id="STAT1">
3 <Stat>
4 <Property name="configName" value="custconfig" />
5 <Property name="descrption" value="企业信息处理" />
6 <Property name="taskType" value="1" />
7 <Property name="expiredMonth" value="6" />
8 <Property name="batchNum" value="500" />
9 </Stat>
10 <Ftp>
11 <Property name="host" value="127.0.0.1" />
12 <Property name="user" value="stateg"/>
13 <Property name="password" value="stateg"/>
14 <Property name="port" value="14147"/>
15 <Property name="remoteDir" value="/cust"/>
16 <Property name="localDir" value="D:/ldata_eg/cu-udr"/>
17 <Property name="fileNamePattern" value="CUST_\d{6}.TXT"/>
18 <Property name="interval" value="10"/>
19 <Property name="reserve" value="true"/>
20 <Property name="maxFileCount" value="100"/>
21 <Property name="checkInterval" value="7200"/>
22 </Ftp>
23 <Table>
24 <Record>
25 <Property name="tablename" value="T_CU_TABLE_NAME" />
26 <Property name="separator" value=" " />
27 <Property name="startrow" value="1" />
28 <Property name="endCol" value="5" />
29 </Record>
30 <Column order="0" prop="ID" type="VARCHAR2" length="32" key="yes" />
31 <Column order="1" prop="CUSTNAME" type="VARCHAR2" length="128" emtpy="yes" />
32 <Column order="2" prop="CUSTAREA" type="VARCHAR2" length="128" emtpy="yes" />
33 <Column order="3" prop="CREATEDATE" type="DATE"/>
34 <Column order="4" prop="PARENTID" type="VARCHAR2" length="32" emtpy="yes" />
35 <Column order="4" prop="UPDATETIME" type="DATE"/>
36 </Table>
37</Config>
38
接下来,就是XML文件的读取了。使用dom4j是无疑的,可参见http://www.blogjava.net/colorfire/articles/338764.html。
1/** *//**
2 * 获取配置对象
3 * @return
4 * @throws DocumentException
5 */
6 public StatConfig getConfigInfo() throws DocumentException {
7 SAXReader saxReader = new SAXReader();
8 Document document = saxReader.read("META-INFO/xml/StatConfigInfo.xml");
9
10 // 配置入库参数
11 StatConfig statConfig = (StatConfig) reflectConfig(warpPropertyMap(document
12 .selectNodes("//Config/Stat/Property")), StatConfig.class);
13
14 // 配置FTP客户端参数
15 FtpConfig ftpConfig = (FtpConfig) reflectConfig(warpPropertyMap(document.selectNodes("//Config/Ftp/Property")),
16 FtpConfig.class);
17
18 // 配置数据库
19 TableConfig tableConfig = (TableConfig) reflectConfig(warpPropertyMap(document
20 .selectNodes("//Config/Table/Record/Property")), TableConfig.class);
21 List list = document.selectNodes("//Config/Table/Column");
22 Iterator iter = list.iterator();
23 Map<String, ColumnType> colMap = new HashMap<String, ColumnType>();
24 while (iter.hasNext()) {
25 Element element = (Element) iter.next();
26 Map<String, String> configMap = warpAttributeMap(element.attributeIterator());
27 ColumnType colum = (ColumnType) reflectConfig(configMap, ColumnType.class);
28 colMap.put(Integer.toString(colum.getOrder()), colum);
29 }
30 tableConfig.setColMap(colMap);
31
32 statConfig.setFtpConfig(ftpConfig);
33 statConfig.setTableConfig(tableConfig);
34 return statConfig;
35 }
由于XML分块,将数据封装单独抽取成方法。
1private Map<String, String> warpPropertyMap(List list) {
2 Map<String, String> configMap = new HashMap<String, String>();
3 Iterator iter = list.iterator();
4 while (iter.hasNext()) {
5 Element element = (Element) iter.next();
6 configMap.put(element.attributeValue("name").toLowerCase(), element.attributeValue("value").toLowerCase());
7 }
8 return configMap;
9 }
最后就是将配置数据封装进对象,这里用反射再合适不过了。
1public Object reflectConfig(Map<String, String> configMap, Class cls) {
2 Object config = null;
3 try {
4 config = cls.newInstance();
5 Field[] fields = cls.getDeclaredFields();
6 for (int i = 0; i < fields.length; i++) {
7 String fieldName = fields[i].getName();
8 Class fieldType = fields[i].getType();
9 Method method = cls.getMethod("set" + genMethodName(fieldName), fieldType);
10 String valueString;
11 if ((valueString = configMap.get(fieldName.toLowerCase())) != null) {
12 if (fieldType.equals(String.class)) {
13 method.invoke(config, valueString);
14 } else if (fieldType.equals(Integer.class)) {
15 method.invoke(config, Integer.parseInt(valueString));
16 } else if (fieldType.equals(Long.class)) {
17 method.invoke(config, Long.parseLong(valueString));
18 } else if (fieldType.equals(Boolean.class)) {
19 method.invoke(config, Boolean.parseBoolean(valueString));
20 } else if (fieldType.equals(Character.class)) {
21 method.invoke(config, valueString.toCharArray()[0]);
22 } else {
23 throw new NoSuchFieldException("FtpConfig没有定义的数据类型");
24 }
25 }
26 }
27 } catch (Exception e) {
28 e.printStackTrace();
29 }
30 return config;
31 }
完成,想测试一下,单独写个测试方法吧。
1/** *//**
2 * 测试方法
3 * @param obj
4 * @return
5 */
6 public static StringBuffer testPOJO(Object obj) {
7 Class cls = obj.getClass();
8 Field[] fields = cls.getDeclaredFields();
9 StringBuffer resultBuf = new StringBuffer();
10 try {
11 for (int i = 0; i < fields.length; i++) {
12 String fieldName = fields[i].getName();
13 Class fieldType = fields[i].getType();
14 Method method;
15 if (fieldType.equals(Boolean.class)) {
16 method = cls.getMethod("is" + genMethodName(fieldName));
17 } else {
18 method = cls.getMethod("get" + genMethodName(fieldName));
19 }
20 Object res;
21 if ((res = method.invoke(obj)) != null) {
22 String result = res.toString();
23 resultBuf.append("[" + fieldName + "] = " + result + "\n");
24 } else {
25 resultBuf.append("[" + fieldName + "] = NULL \n");
26 }
27 }
28 } catch (Exception e) {
29 e.printStackTrace();
30 }
31 return resultBuf;
32 }
总结:本来考虑的是统一配置后,丢弃ibatis,直接动态生成sql语句执行。但字段名与类属性值匹配又是个麻烦,这里用反射的话就太影响性能了。没想出好方法,再考虑考虑。
后续把测试类改成递归的。