使用Java泛型和反射机制编写Excel文件生成和解析的通用工具类

前几天被派到一个小项目中做临时维护,工作地点不方便且不说,项目代码那叫一个恶心...

  1. 代码几乎完全没有注释。这应该是我们天朝大部分程序员的习惯,代码不写注释,给后面维护的同事带来多大麻烦啊!
  2. 几百行的JS代码放在JSP文件中,而且没有格式。个人觉得这么长的代码提取到JS文件中比较好,都堆在JSP中使程序可读性极差!
  3. HTML代码没有结构可言。基本的缩进都没有,读这种代码那叫一个欲哭无泪啊!HTML混合JSTL以及Struts2的标签,叫人头大!
  4. 超长的方法体。接触项目的第三天见到一个600多行的方法,感觉很头大,更极品的是后来发现了一个1100行的方法,当时的感觉是见到高人了...
  5. 文件组织极其混乱。自己写的JS与引入的开源JS框架混在一起,JS开源框架文件也是乱放。
  6. 命名也是非常混乱,完全做不到见名知义。
  7. 代码乱放。本来不是该模块的代码非要组织到该模块中...
  8. 其它问题。如同样代码多处编写等

其中有一个问题就是对Excel文件的导入解析(就是那个600多行的方法),那么长的方法,我都懒得去读一遍;代码长还不是主要问题,主要问题是这种代码肯定是需要一次就得写一次。有鉴于此,我便花些时间试着利用Java的泛型与反射机制写了一个通用的工具类,但绞尽脑汁也没有写地太完善,思来想去总是不尽人意;佑于水平不高,也只能如此,测试了一下勉强能用,只是容错能力不强,发表于此,以待后用...

列名使用粗体、水平居中、垂直居中、12号字,没有其它样式,也没有提供自定义单元格样式的功能,如果有特殊需求,此类不适用。

package com.ninemax.common.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import jxl.BooleanCell;
import jxl.Cell;
import jxl.DateCell;
import jxl.NumberCell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.biff.EmptyCell;
import jxl.format.Alignment;
import jxl.format.VerticalAlignment;
import jxl.write.Blank;
import jxl.write.DateTime;
import jxl.write.Label;
import jxl.write.WritableCellFormat;
import jxl.write.WritableFont;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;

/**
 * 操作Excel工具类(使用开源项目JXL操作Excel).
* 需要严格按照方法定义来使用, 容错能力不高.
* 实体类应该严格按照JavaBean规范定义, 数据类型仅支持基本类型、java.util.Date、java.lang.String * * @author zhangyh (2013-01-11) */ public class ExcelUtil { /** * getExcelStream(T[], String[], String[], String)的中转方法, 可直接接受List集合数据 * @see {@link ExcelUtil#getExcelStream(Object[], String[], String[], String)} */ public static OutputStream getExcelStream(List dataList, String [] beanProperties, String [] columnNames, String sheetName) throws Exception { if (null != dataList && dataList.size() > 0) { Class newType = dataList.get(0).getClass(); @SuppressWarnings("unchecked") T [] tempArray = (T []) Array.newInstance(newType, dataList.size()); dataList.toArray(tempArray); return getExcelStream(tempArray, beanProperties, columnNames, sheetName); } return null; } /** * getExcelStreamWithPointOutProperties(T[], String[], String[], String)的中转方法, * 可直接接受List集合数据, 方法第二个参数接受指明转换到Excel的JavaBean属性, 顺序任意. * @see {@link ExcelUtil#getExcelStreamWithPointOutProperties(Object[], String[], String[], String)} */ public static OutputStream getExcelStreamWithPointOutProperties(List dataList, String [] beanProperties, String [] columnNames, String sheetName) throws Exception { if (null != dataList && dataList.size() > 0) { Class newType = dataList.get(0).getClass(); @SuppressWarnings("unchecked") T [] tempArray = (T []) Array.newInstance(newType, dataList.size()); dataList.toArray(tempArray); return getExcelStreamWithPointOutProperties(tempArray, beanProperties, columnNames, sheetName); } return null; } /** *

将Java对象组成的集合转换成Excel数据, 以输出流形式返回, 可以实现下载、存储在磁盘等.

*

Excel文件列按JavaBean属性定义顺序生成, 还需要注意Excel列名数与排除掉的属性数之和必须与 * JavaBean属性总数相等.

* @param dataList 需要转换到Excel的Java对象数组 * @param columnNames Excel列名(即JavaBean属性对应的列名, 如:name >> '姓名'), 必须按JavaBean * 属性定义顺序给出, 还需要注意跳过excludeProperties指定的排除掉的属性 * @param excludeProperties 排除掉的JavaBean属性名 * @param sheetName Excel文件表名 * @return 生成的Excel文件输出流(ByteArrayOutputStream) * @throws Exception */ public static OutputStream getExcelStream(T [] dataList, String [] columnNames, String [] excludeProperties, String sheetName) throws Exception { if (null == columnNames) { throw new Exception("Excel列名必须指定."); } // 取得数组成员的类型Class对象 Class clazz = dataList.getClass().getComponentType(); Field [] fields = clazz.getDeclaredFields(); List excludePropertyList = Collections.emptyList(); if (excludeProperties != null && excludeProperties.length > 0) { excludePropertyList = Arrays.asList(excludeProperties); } if (fields.length != columnNames.length + excludePropertyList.size()) { throw new Exception("给定Excel列名为" + columnNames.length + "个, 排除掉的" + "属性为" + excludePropertyList.size() + "个, 实体类" + clazz.getSimpleName() + "共有" + fields.length + "个属性,个数不匹配."); } // 列名与Bean有效属性一一对应 String [] beanProperties = new String [columnNames.length]; int i = 0; for(Field field : fields) { String fieldName = field.getName(); if (excludePropertyList == null || !excludePropertyList.contains(fieldName)) { // 当给定排除属性数和列名数之和与Bean属性数不等时会有异常 beanProperties[i++] = fieldName; } } Method [] getterMethods = parseGetterMethods(beanProperties, clazz); return getExcelStream(dataList, getterMethods, columnNames, sheetName); } /** *

将Java对象组成的集合转换成Excel数据, 以输出流形式返回, 可以实现下载、存储在磁盘等.

*

可按任意顺序取任意个数的Bean属性导出到Excel, Excel列顺序将与给定beanProperties顺序保持一致.

* @param data 需要生成Excel文件的Java数据, beanProperties即是T中的属性 * @param beanProperties 需要转换到Excel中的Bean属性 * @param colNames Excel使用的列名, 需要与beanProperties一一对应 * @param sheetName Excel表名 * @return 生成的Excel输出流(ByteArrayOutputStream) * @throws Exception */ public static OutputStream getExcelStreamWithPointOutProperties (T [] data, String [] beanProperties, String [] colNames, String sheetName) throws Exception { if (beanProperties == null || colNames == null) { throw new Exception("必须给出有效的JavaBean属性及相应的Excel列名."); } if (beanProperties.length > colNames.length) { throw new Exception("给出的Excel列名为" + colNames.length + "个, 给出的有效Bean属性为" + beanProperties.length + "个, 个数不匹配."); } Class clazz = data.getClass().getComponentType(); Method [] getterMethods = parseGetterMethods(beanProperties, clazz); return getExcelStream(data, getterMethods, colNames, sheetName); } // 解析JavaBean属性对应的getter方法 private static Method [] parseGetterMethods(String [] properties, Class clazz) throws Exception { Method [] getterMethods = new Method [properties.length]; int i = 0; for(String property : properties) { String getterName = "get" + property.substring(0, 1).toUpperCase() + property.substring(1); try { getterMethods[i++] = clazz.getMethod(getterName, (Class []) null); } catch (SecurityException e) { throw new Exception("属性" + property + "对应的getter方法可能无法访问.", e); } catch (NoSuchMethodException se) { throw new Exception("属性" + property + "没有对应的getter方法.", se); } } return getterMethods; } /** * 具体执行方法, 公开的方法getExcelStreamWithPointOutProperties和getExcelStream都是为调用此方法作相应准备. * 方法中调用getters并将值填进相应的单元格中, 最后将Excel文件以输出流的形式返回. * @param data Java集合数据, 公开方法中的List形式也会转为数组形式. * @param getterMethods 需要转换成Excel列的JavaBean属性对应的getters方法 * @param colNames Excel列名, 与JavaBean属性对应 * @param sheetName Excel表名 * @return Excel文件对应的输出流 * @throws Exception */ private static OutputStream getExcelStream(T [] data, Method [] getterMethods, String [] colNames, String sheetName) throws Exception { if (null == sheetName) { sheetName = "Sheet_1"; } ByteArrayOutputStream os = new ByteArrayOutputStream(); WritableWorkbook workbook = Workbook.createWorkbook(os); WritableSheet sheet = workbook.createSheet(sheetName, 0); WritableCellFormat cellFormat = new WritableCellFormat(); cellFormat.setAlignment(Alignment.CENTRE); cellFormat.setVerticalAlignment(VerticalAlignment.CENTRE); cellFormat.setFont(new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD)); int col = 0; for(Method method : getterMethods) { int row = 0; // 第一行(列名)采用粗体、15号字, 并居中对齐 sheet.addCell(new Label(col, row++, colNames[col], cellFormat)); for(T t : data) { // 调用getter方法, 并将返回值添加到Excel的单元格中 invokeGetterMethod(method, t, sheet, row++, col); } col++; } workbook.write(); workbook.close(); return os; } /** * 调用JavaBean中的取值方法, 并将返回值按照JavaBean属性的数据类型填充到Excel指定的单元格. * @param getterMethod JavaBean中的取值方法Method * @param o 调用Getter方法的JavaBean实例 * @param sheet Excel表 * @param rowIndex Excel表中的行索引 * @param colIndex Excel表中的列索引 * @throws Exception */ private static void invokeGetterMethod(Method getterMethod, Object o, WritableSheet sheet, int rowIndex, int colIndex) throws Exception { Class type = getterMethod.getReturnType(); Object returnVal = getterMethod.invoke(o, (Object []) null); if (null == returnVal) { // getter方法返回值为null sheet.addCell(new Blank(colIndex, rowIndex)); }else if (String.class.isAssignableFrom(type) || char.class.isAssignableFrom(type) || Character.class.isAssignableFrom(type)) { // getter方法返回值为String或char sheet.addCell(new Label(colIndex, rowIndex, returnVal.toString())); } else if (Number.class.isAssignableFrom(type) || double.class.isAssignableFrom(type) || int.class.isAssignableFrom(type) || long.class.isAssignableFrom(type) || short.class.isAssignableFrom(type) || float.class.isAssignableFrom(type)) { // getter方法返回值为数字类型 sheet.addCell(new jxl.write.Number(colIndex, rowIndex, ((Number)returnVal).doubleValue())); } else if (Date.class.isAssignableFrom(type)) { // getter方法返回值为java.util.Date类型 sheet.addCell(new DateTime(colIndex, rowIndex, (Date) returnVal)); } else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) { // getter方法返回值为布尔类型 sheet.addCell(new jxl.write.Boolean(colIndex, rowIndex, (Boolean) returnVal)); } else { // 不支持其它的返回值类型 throw new Exception("getter方法: " + getterMethod.getName() + "的返回值类型不被支持."); } } /** *

解析Excel, 将Excel扁平数据解析封装为Java对象, 以Java集合形式返回.

*

由于封装并不完善, 需要严格按照方法指定调用方式使用才可. 解析顺序必须按照Excel文件中列顺序

* @param excel 需要解析的Excel文件 * @param beanProperties JavaBean的属性名, 以数组方式给出; 必须依照Excel文件所定义列顺序给出. * @param columnTypes JavaBean属性的数据类型, 必需与columns数组数据一一对应 * @param clazz JavaBean的Class对象, 必须指定, 否则无法使用反射 * @return Excel解析所得JavaBean组成的List * @throws Exception */ public static List parseExcel(File excel, String [] beanProperties, Class [] propertyTypes, Class clazz) throws Exception { return parseExcel(new FileInputStream(excel), beanProperties, propertyTypes, clazz); } /** * 解析Excel, 将Excel扁平数据解析封装为Java对象. * @param inputStream Excel文件对应的输入流. * @param columns JavaBean的属性名 * @param columnTypes JavaBean属性的数据类型 * @param clazz JavaBean类的Class对象 * @return Excel解析所得JavaBean组成的List * @throws Exception * @see {@link ExcelUtil#parseExcel(File, String[], Class[], Class)} */ public static List parseExcel(InputStream inputStream, String [] beanProperties, Class [] propertyTypes, Class clazz) throws Exception { if (beanProperties == null || propertyTypes == null) { throw new Exception("必须指定Excel列映射的JavaBean属性及相应的数据类型."); } if (beanProperties.length > propertyTypes.length) { throw new Exception("给定的JavaBean属性为" + beanProperties.length + "个, 数据类型为" + propertyTypes.length + "个, 个数不匹配."); } Method [] setMethods = new Method [beanProperties.length]; int i = 0; for(String property : beanProperties) { String setMethodName = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); try { setMethods[i] = clazz.getDeclaredMethod(setMethodName, propertyTypes[i++]); } catch (SecurityException se) { throw new Exception("属性" + property + "对应的setter方法可能无法访问.", se); } catch (NoSuchMethodException e) { String paramType = propertyTypes[--i].getName(); throw new Exception("属性" + property + "没有参数类型为" + paramType + "的setter方法.", e); } } return parseExcel(inputStream, setMethods, propertyTypes, clazz); } /** * 具体解析方法, 由公共方法准备好数据后调用, 因此不公开. * @param inputStream 准备解析的Excel文件对应的输入流 * @param setMethods setter方法数组, 与给定列顺序一样 * @param propertyTypes JavaBean属性类型数组 * @param cls JavaBean类的Class对象 * @return 解析成功后JavaBean集合 * @throws Exception */ private static List parseExcel(InputStream inputStream, Method [] setterMethods, Class [] propertyTypes, Class beanType) throws Exception { Workbook workbook = Workbook.getWorkbook(inputStream); Sheet sheet = workbook.getSheet(0); List parseResultList = new ArrayList(sheet.getRows() - 1); for(int row = 1; row < sheet.getRows(); row++) { // 跳过第0行, 一般第0行是列名 T instance = beanType.newInstance(); // 创建实例 Cell [] cells = sheet.getRow(row); for(Cell cell : cells) { if (cell.getColumn() >= setterMethods.length) break; Method setMethod = setterMethods[cell.getColumn()]; Class type = propertyTypes[cell.getColumn()]; if (null != setMethod) { // 调用新创建实例的setter invokeSetterMethod(setMethod, instance, cell, type); } } parseResultList.add(instance); } workbook.close(); return parseResultList; } /** * 取出Excel单元格中的数据并作相应转换, 然后调用实例的设置属性值方法(setters), 给实例设置指定值 * @param setterMethod setter方法 * @param o setterMethod调用所针对的实例 * @param cell Excel单元格 * @param paramType setter方法所需参数的类型 * @throws Exception */ private static void invokeSetterMethod(Method setterMethod, Object o, Cell cell, Class paramType) throws Exception { try { if (cell instanceof EmptyCell) { // 单元格没有内容, 将实体类相应属性设置为null setterMethod.invoke(o, new Object [] { null }); } else if (String.class.isAssignableFrom(paramType) || char.class.isAssignableFrom(paramType) || Character.class.isAssignableFrom(paramType)) { // String|char|Character设置为String setterMethod.invoke(o, cell.getContents()); } else if (Double.class.isAssignableFrom(paramType) || double.class.isAssignableFrom(paramType)) { // Double|double Double number = ((NumberCell) cell).getValue(); setterMethod.invoke(o, number); } else if (Integer.class.isAssignableFrom(paramType) || int.class.isAssignableFrom(paramType)) { // Integer|int Double number = ((NumberCell) cell).getValue(); setterMethod.invoke(o, number.intValue()); } else if (Long.class.isAssignableFrom(paramType) || long.class.isAssignableFrom(paramType)) { // Long|long Double number = ((NumberCell) cell).getValue(); setterMethod.invoke(o, number.longValue()); } else if (Float.class.isAssignableFrom(paramType) || float.class.isAssignableFrom(paramType)) { // Float|float Double number = ((NumberCell) cell).getValue(); setterMethod.invoke(o, number.floatValue()); } else if (Short.class.isAssignableFrom(paramType) || short.class.isAssignableFrom(paramType)) { // Short|short Double number = ((NumberCell) cell).getValue(); setterMethod.invoke(o, number.shortValue()); } else if (Boolean.class.isAssignableFrom(paramType) || boolean.class.isAssignableFrom(paramType)) { // Boolean|boolean setterMethod.invoke(o, ((BooleanCell)cell).getValue()); } else if (Date.class.isAssignableFrom(paramType)) { // java.util.Date setterMethod.invoke(o, ((DateCell)cell).getDate()); } else { throw new Exception("方法" + setterMethod.getName() + "所需要的参数类型不被支持."); } } catch (IllegalArgumentException e) { throw new Exception("setter方法" + setterMethod.getName() + "需要参数的类型为" + setterMethod.getParameterTypes()[0].getName() + ", 传入的类型为" + paramType.getName(), e); } } }


用Java时间也不短了,但对其一些高级特性总是不太明白,这其中就包括反射和泛型;也许有人觉得泛型不算高级特性,因为我们经常会接触到它,但我们真的理解它了吗?看《Thinking In Java》的时候略有所悟,但放下书本后又觉得还是不明白,也许经常写一些类似这种的通用代码,对它们的理解会更深一点儿...

你可能感兴趣的:(Java)