项目里有一个功能,是将数据库查询出的数据全部导出为excel报表,由于数据复杂,使用的是jxls模板,就是这样:
jxls代码是这样的:
// 获得输出的响应
HttpServletResponse response = this.getResponse();
HSSFWorkbook workbook = JxlsManager.getWorkBookByTemplate(
"template.xls", dataList, "dataVo");
JxlsManager.exportExcel(workbook, response, "导出数据清单");
JxlsManager为自己写的工具类,此处用到的方法内容如下
//从一个Excel模板中得到一个WORKBOOK
public static HSSFWorkbook getWorkBookByTemplate(String templateFileName,
Object exportObj, String objectName) throws Exception {
//获取Excel模板的全路径(此处根据实际情况替换为拼成路径的方法)
templateFileName = new JxlsManager().getPath()+templateFileName;
templateFileName = "/" + templateFileName;
Map beans = new HashMap();
beans.put(objectName, exportObj);
XLSTransformer transformer = new XLSTransformer();
InputStream is = new BufferedInputStream(new FileInputStream(
templateFileName));
HSSFWorkbook workbook = transformer.transformXLS(is, beans);
return workbook;
}
//此方法用来将workbook输出到页面上,提供给用户下载
public static void exportExcel(HSSFWorkbook workBook, HttpServletResponse response, String excelName) throws Exception {
//输出文件的格式
response.setContentType("application/vnd.ms-excel");
OutputStream os = response.getOutputStream();
//处理名字为中文的问题
excelName = new String(excelName.getBytes("GB18030"),"ISO8859-1");
//设置导出文件的名字
response.addHeader("Content-Disposition", "attachment; filename=\""+excelName+".xls\"");
workBook.write(os);
os.flush();
os.close();
}
Jxls可以非常灵活地读取Excel模板的EL表达式,所以非常方便,但是速度很慢,所以作者接到了这个需求,将Jxls技术换成效率很高的POI。
那么问题来了,如何使用POI来读取模板中的EL表达式呢,我的第一反应是使用反射,代码如下:
/**
* 功能:完成POI部分的公用功能
*/
public class POIManager {
/**
* 从一个样例中得到一个WORKBOOK
*
* @param templateFileName
* @return
* @throws Exception
*/
public static HSSFWorkbook getHSSFWorkBookByTemplate(String templateFileName) throws Exception {
File template = new File("/" + new POIManager().getPath() + templateFileName);
FileInputStream fis;
fis = new FileInputStream(template);
HSSFWorkbook wb = new HSSFWorkbook(fis);
fis.close();
return wb;
}
/** 获得模板路径 */
public String getPath() {
URL url = getClass().getClassLoader().getResource("/");
String strFilePath = "";
if (strFilePath != null) {// 保证不为空
strFilePath += url.toString();
if (strFilePath.startsWith("file:") && strFilePath.length() > 6) {// 保证windows环境
strFilePath = strFilePath.substring(6);
}
if (strFilePath.indexOf("WEB-INF/classes") > -1) {
strFilePath = strFilePath.substring(0, strFilePath.indexOf("WEB-INF/classes"));
}
}
return strFilePath += "common/xls/export/";
}
/**
* 读取模板中的EL表达式,获得所有需要反射执行的Method,存入Map
* @param templateFileName
* @return
* @throws Exception
*/
@SuppressWarnings({ "rawtypes" })
public Map<Integer, Method> getMethodsMap(String templateFileName) throws Exception {
File template = new File("/" + getPath() + templateFileName);
// 读取模板中定义el表达式的一行
List methods = ExcelImportUtil.readExcelRow(template, 3);
// 获取所有的需要反射执行的Method方法顺序放入map
Map<Integer, Method> map = new HashMap<Integer, Method>();
for (int i = 0; i < methods.size(); i++) {
String methodName = (String) methods.get(i);
// 将methodName从${body.xxx}变为getXxx
if (methodName != "" && methodName != null) {
String[] split = methodName.split("\\.");
methodName = split[1].replace('}', ' ').trim();
String upperCase = methodName.substring(0, 1).toUpperCase();
methodName = upperCase + methodName.substring(1);
methodName = "get" + methodName;
Class<?> clazz = null;
if(templateFileName=="prpCcargoPolicyVos.xls"){
clazz = PrpCcargoPolicyVo.class;
}
else if(templateFileName=="aviationPrpCPolicy.xls"){
clazz = PrpCcargoPolicyVo.class;
}
else{
clazz = PrpCpolicyVo.class;
}
Method method = clazz.getMethod(methodName);
map.put(i, method);
} else {
continue;
}
}
return map;
}
/**
* 获得cellstyle
*
* @param wb
* @return
*/
public static CellStyle getCellStyle(HSSFWorkbook wb) {
CellStyle style = wb.createCellStyle();
Font font = wb.createFont();
font.setFontName("宋体");
font.setFontHeightInPoints((short) 10);
style.setFont(font);
// style1.setFillBackgroundColor(HSSFColor.LIGHT_YELLOW);
style.setAlignment(CellStyle.ALIGN_CENTER); // 横向居中
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); // 纵向居中
style.setBorderTop(CellStyle.BORDER_THIN); // 上细线
style.setBorderBottom(CellStyle.BORDER_THIN); // 下细线
style.setBorderLeft(CellStyle.BORDER_THIN); // 左细线
style.setBorderRight(CellStyle.BORDER_THIN);
return style;
}
/**
* 输出Excel
* @param response
* @param wb
* @param dataList
* @param methodsMap
* @throws Exception
*/
public static void exportExcel(HttpServletResponse response, HSSFWorkbook wb, List dataList,
Map<Integer, Method> methodsMap,String excelName) throws Exception {
// 获取第一页sheet
HSSFSheet sheet = wb.getSheetAt(0);
//获取cellStyle
CellStyle cellStyle = getCellStyle(wb);
for (int i = 0; i < dataList.size(); i++) {
//===============================================
Object pr = null;
pr = (DataVo) dataList.get(i);
//===============================================
HSSFRow row = sheet.createRow(i + 1);
row.setHeightInPoints(36);
for (int j = 0; j < methodsMap.size(); j++) {
HSSFCell cell = row.createCell(j);
cell.setCellStyle(cellStyle);
Method method = methodsMap.get(j);
if (method == null) {
continue;
}
Object invoke = method.invoke(pr);
// 判断方法返回值类型,对返回值invoke类型强转
Class<?> returnType = method.getReturnType();
if (invoke == null) {
cell.setCellValue("");
continue;
}
if (returnType == String.class) {
if ((String) invoke == "") {
cell.setCellValue("");
} else {
String str = (String) invoke;
str = str.trim();
cell.setCellValue(str);
}
}
if (returnType == Double.class) {
cell.setCellValue((Double) invoke);
}
if (returnType == Integer.class) {
cell.setCellValue((Integer) invoke);
}
if (returnType == Date.class) {
Date dateTemp = (Date) invoke;
cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(dateTemp).toString());
}
}
}
response.setContentType("application/vnd.ms-excel");
OutputStream os = response.getOutputStream();
// 处理名字为中文的问题
excelName = new String(excelName.getBytes("GB18030"), "ISO8859-1");
// 设置导出文件的名字
response.addHeader("Content-Disposition", "attachment; filename=\"" + excelName + ".xls\"");
wb.write(os);
os.flush();
os.close();
}
}
这样就初步完成了POI读取模板中EL表达式完成导出的情况,因为是反射,所以速度肯定会慢一些,我想应该还有比反射更好的手段,只不过目前已经比Jxls速率快多了,我在本地测试,3w条数据,Jxls大概要190s,而POI只需要2-5s,简直是碾压的差距。
不过相比Jxls,POI有个致命的弱点就是不能实现读取EL表达式,如果模板复杂,像下面这样,POI读模板就不好实现,需要重写方法,我的决定是分情况决定技术,简单模板使用poi提升速度,复杂模板就使用jxls。
这样的话,就需要项目中同时支持POI与Jxls两种技术。但是问题来了,原本项目中使用的是poi-3.2的jar包来支持jxls-0.9.8,我为了使用POI,导入了poi-3.5的jar包,引入poi-3.5就必然要删除poi-3.2,但是poi-3.5的jar包无法支持jxls中这个方法
transformer.transformXLS(is, beans);
所以要么使用jxls,速度慢,要么使用POI,没有jxls灵活,无法实现完全自由的读模板,一旦遇到复杂模板就很难处理。
那么有没有什么方法能够让两个不同版本poi的jar包共存呢,有,那就是自定义jar包!
冲突的原因就是因为不同版本的jar包,其中的类路径和方法名都相同,那么我们可以将新引入的poi-3.5的包名修改为orgX.apache...(随便怎么改都行)
在百度之后,我发现了一个叫做jarjar.jar的工具jar包,可以修改jar包的包名。大家可以去百度使用方法,这里就不赘述了。
【这里有一个小插曲,jarjar.jar这个包网上提供下载的不多,推荐大家去http://www.java2s.com/这个网站下载,jar包和其他编程资源比较全。】
将jar包修改过之后,命名为poiX-3.5.jar
在JxlsManager中导入jar包不变,而POIManager中导入jar包路径为orgX.apache,这样就不会冲突了,如下所示
import orgX.apache.poi.hssf.usermodel.HSSFCell;
import orgX.apache.poi.hssf.usermodel.HSSFRow;
import orgX.apache.poi.hssf.usermodel.HSSFSheet;
import orgX.apache.poi.hssf.usermodel.HSSFWorkbook;
import orgX.apache.poi.ss.usermodel.CellStyle;
import orgX.apache.poi.ss.usermodel.Font;
用哪个导哪个,亲测有效。
其实自定义jar包根本不难,难的是很多时候思维被jar包束缚住,没想到可以改jar包,所以编程既需要技术,更需要思维的开阔啊/捂脸。