最近在研究EasyPoi的excel导出功能,发现其不支持表头各个单元格样式自定义,所以就对其导出功能进行了重写。
一、EasyPoi源码分析
在进行重写之前,首先要了解EasyPoi源码中,对表头处理的代码具体是如何实现的。查看源码可以看出,导出功能主要是通过ExcelExportService类来实现,而在其中可以看到向excel中填入表头及数据的方法为insertDataToSheet,具体代码如下:
protected void insertDataToSheet(Workbook workbook, ExportParams entity,
List<ExcelExportEntity> entityList, Collection<?> dataSet,
Sheet sheet) {
try {
dataHandler = entity.getDataHandler();
if (dataHandler != null && dataHandler.getNeedHandlerFields() != null) {
needHandlerList = Arrays.asList(dataHandler.getNeedHandlerFields());
}
dictHandler = entity.getDictHandler();
commentHandler = entity.getCommentHandler();
// 创建表格样式
setExcelExportStyler((IExcelExportStyler) entity.getStyle()
.getConstructor(Workbook.class).newInstance(workbook));
Drawing patriarch = PoiExcelGraphDataUtil.getDrawingPatriarch(sheet);
List<ExcelExportEntity> excelParams = new ArrayList<ExcelExportEntity>();
if (entity.isAddIndex()) {
excelParams.add(indexExcelEntity(entity));
}
excelParams.addAll(entityList);
sortAllParams(excelParams);
int index = entity.isCreateHeadRows()
? createHeaderAndTitle(entity, sheet, workbook, excelParams) : 0;
int titleHeight = index;
setCellWith(excelParams, sheet);
setColumnHidden(excelParams, sheet);
short rowHeight = entity.getHeight() != 0 ? entity.getHeight() : getRowHeight(excelParams);
setCurrentIndex(1);
createAddressList(sheet, index, excelParams, 0);
Iterator<?> its = dataSet.iterator();
List<Object> tempList = new ArrayList<Object>();
while (its.hasNext()) {
Object t = its.next();
index += createCells(patriarch, index, t, excelParams, sheet, workbook, rowHeight, 0)[0];
tempList.add(t);
if (index >= MAX_NUM) {
break;
}
}
if (entity.getFreezeCol() != 0) {
sheet.createFreezePane(entity.getFreezeCol(), 0, entity.getFreezeCol(), 0);
}
mergeCells(sheet, excelParams, titleHeight);
its = dataSet.iterator();
for (int i = 0, le = tempList.size(); i < le; i++) {
its.next();
its.remove();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("List data more than max ,data size is {}",
dataSet.size());
}
// 发现还有剩余list 继续循环创建Sheet
if (dataSet.size() > 0) {
createSheetForMap(workbook, entity, entityList, dataSet);
} else {
// 创建合计信息
addStatisticsRow(getExcelExportStyler().getStyles(true, null), sheet);
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e);
}
}
而在此方法中进一步分析,可看出表头数据及样式操作的方法为createHeaderAndTitle,跳转后可确定操作表头方法为createHeaderRow,具体代码如下:
private int createHeaderRow(ExportParams title, Sheet sheet, Workbook workbook, int index,
List<ExcelExportEntity> excelParams, int cellIndex) {
Row row = sheet.getRow(index) == null ? sheet.createRow(index) : sheet.getRow(index);
int rows = getRowNums(excelParams, true);
row.setHeight(title.getHeaderHeight());
Row listRow = null;
if (rows >= 2) {
listRow = sheet.getRow(index + 1);
if (listRow == null) {
listRow = sheet.createRow(index + 1);
listRow.setHeight(title.getHeaderHeight());
}
}
int groupCellLength = 0;
CellStyle titleStyle = getExcelExportStyler().getTitleStyle(title.getColor());
for (int i = 0, exportFieldTitleSize = excelParams.size(); i < exportFieldTitleSize; i++) {
ExcelExportEntity entity = excelParams.get(i);
// 加入换了groupName或者结束就,就把之前的那个换行
if (StringUtils.isBlank(entity.getGroupName()) || i == 0 || !entity.getGroupName().equals(excelParams.get(i - 1).getGroupName())) {
if (groupCellLength > 1) {
sheet.addMergedRegion(new CellRangeAddress(index, index, cellIndex - groupCellLength, cellIndex - 1));
}
groupCellLength = 0;
}
if (StringUtils.isNotBlank(entity.getGroupName())) {
createStringCell(row, cellIndex, entity.getGroupName(), titleStyle, entity);
createStringCell(listRow, cellIndex, entity.getName(), titleStyle, entity);
groupCellLength++;
} else if (StringUtils.isNotBlank(entity.getName())) {
createStringCell(row, cellIndex, entity.getName(), titleStyle, entity);
}
if (entity.getList() != null) {
// 保持原来的
int tempCellIndex = cellIndex;
cellIndex = createHeaderRow(title, sheet, workbook, rows == 1 ? index : index + 1, entity.getList(), cellIndex);
List<ExcelExportEntity> sTitel = entity.getList();
if (StringUtils.isNotBlank(entity.getName()) && sTitel.size() > 1) {
PoiMergeCellUtil.addMergedRegion(sheet, index, index, tempCellIndex, tempCellIndex + getFieldLength(sTitel));
}
/*for (int j = 0, size = sTitel.size(); j < size; j++) {
createStringCell(rows == 2 ? listRow : row, cellIndex, sTitel.get(j).getName(),
titleStyle, entity);
cellIndex++;
}*/
cellIndex--;
} else if (rows > 1 && StringUtils.isBlank(entity.getGroupName())) {
createStringCell(listRow, cellIndex, "", titleStyle, entity);
PoiMergeCellUtil.addMergedRegion(sheet, index, index + rows - 1, cellIndex, cellIndex);
}
cellIndex++;
}
if (groupCellLength > 1) {
PoiMergeCellUtil.addMergedRegion(sheet, index, index, cellIndex - groupCellLength, cellIndex - 1);
}
return cellIndex;
}
分析源码可看出,表头的样式主要是根据excelParams参数中存储的样式来设定,而此参数对应的类型(ExcelExportEntity)中,样式字段只有宽度和高度,而这就是EasyPoi不支持自定义表头样式的原因。
通过上述分析,我们可知,若要支持自定义表头样式,我们首先考虑在createHeaderRow方法的中增加表头样式的参数(List<ExcelExportExtendEntity> extendList),而此方法为private方法,所以只能在继承ExcelExportService类的自定义类中重载此方法。继而,需要依次重载createHeaderAndTitle、insertDataToSheet、createSheetForMap。而之所以要依次重载这些方法,主要原因在于,表头自定义样式的获取最好和本身excelParams参数内容的获取放在一起,防止重复调用。而excelParams参数内容是通过getAllExcelField方法来获取,源码如下:
public void getAllExcelField(String[] exclusions, String targetId, Field[] fields,
List<ExcelExportEntity> excelParams, Class<?> pojoClass,
List<Method> getMethods, ExcelEntity excelGroup) throws Exception {
List<String> exclusionsList = exclusions != null ? Arrays.asList(exclusions) : null;
ExcelExportEntity excelEntity;
// 遍历整个filed
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
// 先判断是不是collection,在判断是不是java自带对象,之后就是我们自己的对象了
if (PoiPublicUtil.isNotUserExcelUserThis(exclusionsList, field, targetId)) {
continue;
}
// 首先判断Excel 可能一下特殊数据用户回自定义处理
if (field.getAnnotation(Excel.class) != null) {
Excel excel = field.getAnnotation(Excel.class);
String name = PoiPublicUtil.getValueByTargetId(excel.name(), targetId, null);
if (StringUtils.isNotBlank(name)) {
excelParams.add(createExcelExportEntity(field, targetId, pojoClass, getMethods, excelGroup));
}
} else if (PoiPublicUtil.isCollection(field.getType())) {
ExcelCollection excel = field.getAnnotation(ExcelCollection.class);
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> clz = (Class<?>) pt.getActualTypeArguments()[0];
List<ExcelExportEntity> list = new ArrayList<ExcelExportEntity>();
getAllExcelField(exclusions,
StringUtils.isNotEmpty(excel.id()) ? excel.id() : targetId,
PoiPublicUtil.getClassFields(clz), list, clz, null, null);
excelEntity = new ExcelExportEntity();
excelEntity.setName(PoiPublicUtil.getValueByTargetId(excel.name(), targetId, null));
if (i18nHandler != null) {
excelEntity.setName(i18nHandler.getLocaleName(excelEntity.getName()));
}