最近在项目中,需要做一个Excel导出功能,当时由于时间比较紧,忙于交互,因此草草的把功能实现就完了,做的比较Lower,我们都知道,Excel导出,一般都会有表头和内容两部分,开始做的时候,表头是使用数组的形式写死,内容部分,笨办法一个一个填充每个单元格的内容,当时的代码是这样的
// 这是定义表头的数组
private final String[] titles = { "发货人", "联系方式", "提货时间", "发货地址", "详细地址","目的地", "目的仓", "交仓平台", "件数(PCS)", "重量(KG)", "体积(m3)", "是否装卸", "备注" };
// 表头填充内容
for (int i = 0; i < titles.length; i++) {
cell = headRow.createCell(i);
cell.setCellStyle(headStyle);
cell.setCellValue(titles[i]);
}
而内容部分是这样子的
for (int i = 0; i < orderList.size(); i++) {
// 自动设置列宽
bodyRow = sheet.createRow(i + 1);
// 发货人
cell = bodyRow.createCell(0);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getConsignor());
// 联系方式
cell = bodyRow.createCell(1);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getPhone());
// 提货时间
cell = bodyRow.createCell(2);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getDeliveryDate());
// 发货地址
cell = bodyRow.createCell(3);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getFromProvinceName()
+ orderList.get(i).getFromCityName()
+ orderList.get(i).getFromAreaName());
// 详细地址
cell = bodyRow.createCell(4);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getFromDetailAddress());
// 目的地
cell = bodyRow.createCell(5);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getToProvinceName()
+ orderList.get(i).getToCityName()
+ orderList.get(i).getToAreaName());
// 目的仓
cell = bodyRow.createCell(6);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getToWarehouse());
// 交仓平台
cell = bodyRow.createCell(7);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getDeliveryPlatform());
// 件数
cell = bodyRow.createCell(8);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getNumber());
// 重量
cell = bodyRow.createCell(9);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getWeight());
// 体积
cell = bodyRow.createCell(10);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getVolume());
// 是否装卸
cell = bodyRow.createCell(11);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getLoadFlag());
// 备注
cell = bodyRow.createCell(12);
cell.setCellStyle(bodyStyle);
cell.setCellValue(orderList.get(i).getComments());
}
内容部分的每个单元格都是一个一个创建的,如果有100列,那就要从0写到99,是不是感觉很Lower啊,实在是忍受不了,决定对这块代码进行重构,想了另一个实现方式,我的思路是:
1、提供一个xml的配置文件,xml中定义好Sheet页名称,对应的实体对象,需要导出的列,列名称,及每一列对应的实体对象属性
2、解析XML,封装列头
3、通过反射,执行列对应属性的get方法,得到每一列的内容
4、以流的形式输出给用户,生成Excel
下面来看具体的实现
1、在resources/excel资源文件目录下新建xuanju.export.xml配置文件,内容如下
<?xml version="1.0" encoding="UTF-8"?>
<export>
<sheet id="exportOrderList" name="order" class="com.xuanju.entity.order.Order">
<column title="发布人" name="Consignor"></column>
<column title="联系方式" name="Phone"></column>
<column title="发货时间" name="DeliveryDate"></column>
<column title="发货地址" name="FromProvinceCode"></column>
</sheet>
</export>
sheet:一个sheet页
id:sheet页ID,一定要唯一,标识按哪个模板导出
name:Sheet页的名称
class:对应的实体对象
column:导出的列
title:列头的名称
name:当前列需要以哪个实体对象的属性填充,例如name=”Consignor”,表进当前列以Order.getConsignor()的值填充,注意name值首字母一定要大写,反射执行get方法时会用到
2、增加一个导出的工具类ExportExcelUtil,在类中提供导出的方法
public static <T> void exportExcel(List<T> list, String sheetId,
HttpServletResponse response) throws Exception {
try {
// 根据sheetId到xuanju.export.xml文件中找,需要按哪个模块导出,并返回对应的DOM树
Element element = readXML(sheetId);
List<Element> columns = element.elements();
XSSFWorkbook wb = new XSSFWorkbook();
// 创建一个Sheet页
XSSFSheet sheet = wb.createSheet(element.attributeValue(("name")));
// 创建表头
createTitleRow(wb, sheet, columns);
// 创建表体
createContentRow(list, sheet, columns,element.attributeValue("class"));
// 输出
outputString(wb, response);
} catch (Exception e) {
throw new Exception("Export excel error.");
}
}
// 根据ID找到对应的导出模板,并返回对应的DOM树
private static Element readXML(String sheetId) throws DocumentException {
String path = "/Users/longwentao/java/svn_code/xuanju/trunk/Code/xuanju.web/src/main/resources/excel/xuanju.export.xml";
SAXReader reader = new SAXReader();
Document document = reader.read(new File(path));
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
Element element = null;
while (iterator.hasNext()) {
element = iterator.next();
if (sheetId.equals(element.attributeValue("id"))) {
break;
}
}
return element;
}
// 根据column创建表头,并根据title设置表头的名称
private static void createTitleRow(XSSFWorkbook wb, XSSFSheet sheet,
List<Element> columns) {
// 创建表头
XSSFRow headRow = sheet.createRow(0);
XSSFCellStyle headStyle = getHeadStyle(wb);
Cell cell = null;
for (int i = 0; i < columns.size(); i++) {
cell = headRow.createCell(i);
cell.setCellStyle(headStyle);
cell.setCellValue(columns.get(i).attributeValue("title"));
}
}
/**
* 创建内容行
* @param list 数据实体列表
* @param sheet Sheet页
* @param columns 导出的列
* @param clazzString 配置文件中Sheet页上的class值
* @throws Exception
*/
private static <T> void createContentRow(List<T> list, XSSFSheet sheet,
List<Element> columns, String clazzString) throws Exception {
// 反射创建对象实例
Class<? extends Object> clazz = Class.forName(clazzString);
Row contentRow = null;
Cell contentCell = null;
Method method = null;
String cellValue = null;
Object obj = null;
// 根据list确认行数
for (int i = 0; i < list.size(); i++) {
contentRow = sheet.createRow(i + 1);
// 根据column确认列
for (int j = 0; j < columns.size(); j++) {
// 使用反射执行当前列对应实体属性的get方法获得值,并填充到对应的单元格中
method = clazz.getMethod("get"+ columns.get(j).attributeValue("name"));
obj = method.invoke(list.get(i));
if (!CommonUtils.isNull(obj)) {
cellValue = obj.toString();
}
contentCell = contentRow.createCell(j);
contentCell.setCellValue(cellValue);
}
}
}
// 导出
private static void outputString(XSSFWorkbook wb,
HttpServletResponse response) throws IOException {
BufferedOutputStream bufferedOutPut = null;
try {
setResponseAttr(response);
bufferedOutPut = new BufferedOutputStream(
response.getOutputStream());
bufferedOutPut.flush();
wb.write(bufferedOutPut);
bufferedOutPut.close();
} catch (Exception e) {
} finally {
if (CommonUtils.isNull(bufferedOutPut)) {
bufferedOutPut.close();
}
}
}
// 设置导出的一些属性
private static void setResponseAttr(HttpServletResponse response) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmssms");
String dateStr = sdf.format(new Date());
// 指定下载的文件名
response.setHeader("Content-Disposition", "attachment;filename="
+ dateStr + ".xlsx");
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
}
// 表头样式
private static XSSFCellStyle getHeadStyle(XSSFWorkbook wb) {
// 创建单元格样式
XSSFCellStyle cellStyle = wb.createCellStyle();
// 设置单元格的背景颜色为淡蓝色
cellStyle.setFillForegroundColor(HSSFColor.PALE_BLUE.index);
cellStyle.setFillPattern(XSSFCellStyle.SOLID_FOREGROUND);
// 设置单元格居中对齐
cellStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER);
// 设置单元格垂直居中对齐
cellStyle.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
// 创建单元格内容显示不下时自动换行
cellStyle.setWrapText(true);
// 设置单元格字体样式
XSSFFont font = wb.createFont();
// 设置字体加粗
font.setBoldweight(XSSFFont.BOLDWEIGHT_BOLD);
font.setFontName("宋体");
font.setFontHeight((short) 200);
cellStyle.setFont(font);
// 设置单元格边框为细线条
cellStyle.setBorderLeft(XSSFCellStyle.BORDER_THIN);
cellStyle.setBorderBottom(XSSFCellStyle.BORDER_THIN);
cellStyle.setBorderRight(XSSFCellStyle.BORDER_THIN);
cellStyle.setBorderTop(XSSFCellStyle.BORDER_THIN);
return cellStyle;
}
3、在Controller层中,直接调用上面的导出接口就可以了
@Autowired
private IOrderService orderService;
List<Order> orderList = orderService.queryOrderList(null);
ExportExcelUtil.exportExcel(orderList, "exportOrderList", response);