EasyExcel钩子(Handler)机制:深度定制 Excel 写入过程
EasyExcel 作为高效的 Excel 处理框架,不仅提供了简单的读写 API,还通过钩子机制(Handler) 允许开发者在 Excel 写入的关键节点(如 Workbook 创建、Sheet 初始化、行 / 单元格生成等)插入自定义逻辑。本文将详细解析 EasyExcel 中的四大钩子接口(Workbook、Sheet、Row、Cell),并通过实例演示如何利用钩子实现复杂需求(如自定义样式、数据校验、动态调整结构等)。
钩子机制的核心作用
钩子机制(Handler)是 EasyExcel 提供的扩展点,允许开发者在 Excel 写入的生命周期节点中嵌入自定义代码,实现以下功能:
- 自定义样式(如表头加粗、奇数行变色、特定单元格高亮);
- 数据校验(如单元格值超出范围时报错或自动修正);
- 动态调整结构(如根据数据内容新增列、合并单元格);
- 资源管理(如在 Workbook 关闭后释放临时资源)。
EasyExcel 将写入过程划分为四个层级的生命周期,对应四类钩子接口,层级关系如下:
1
| Workbook(工作簿) → Sheet(工作表) → Row(行) → Cell(单元格)
|
四大钩子接口详解
WorkbookWriteHandler:工作簿级钩子
作用于整个 Excel 工作簿的创建前后及销毁阶段,用于全局配置(如设置工作簿属性、加密文件等)。
核心方法
方法名 |
触发时机 |
用途示例 |
beforeWorkbookCreate |
工作簿创建前 |
配置工作簿全局参数(如版本) |
afterWorkbookCreate |
工作簿创建后 |
设置工作簿加密密码 |
afterWorkbookDispose |
工作簿所有操作完成并关闭后 |
释放全局资源 |
SheetWriteHandler:工作表级钩子
作用于单个 Sheet 的创建前后,用于配置 Sheet 特性(如设置默认列宽、隐藏 Sheet 等)。
核心方法
方法名 |
触发时机 |
用途示例 |
beforeSheetCreate |
Sheet 创建前 |
指定 Sheet 索引、名称 |
afterSheetCreate |
Sheet 创建后 |
设置默认列宽、冻结首行 |
RowWriteHandler:行级钩子
作用于行的创建前后及销毁阶段,用于行级定制(如行高调整、行样式统一等)。
核心方法
方法名 |
触发时机 |
用途示例 |
beforeRowCreate |
行创建前 |
预设行高、判断是否需要跳过此行 |
afterRowCreate |
行创建后 |
调整行高、设置行背景色 |
afterRowDispose |
行所有操作完成后 |
行数据校验、记录行索引 |
CellWriteHandler:单元格级钩子
作用于单元格的创建、数据转换及销毁阶段,是最常用的钩子,用于单元格样式、数据处理等细节定制。
核心方法
方法名 |
触发时机 |
用途示例 |
beforeCellCreate |
单元格创建前 |
预设单元格类型(文本 / 数字) |
afterCellCreate |
单元格创建后 |
设置单元格边框、对齐方式 |
afterCellDataConverted |
单元格数据转换为 Excel 格式后 |
修正数据格式(如日期格式化) |
afterCellDispose |
单元格所有操作完成后 |
高亮特定值(如错误数据标红) |
实战:自定义钩子实现复杂需求
以下通过三个典型案例,演示如何使用钩子接口解决实际问题。
案例 1:表头样式定制(使用 CellWriteHandler)
需求:表头文字加粗、背景色设为浅灰色,对齐方式居中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*;
public class HeaderStyleHandler implements CellWriteHandler {
@Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if (Boolean.TRUE.equals(isHead)) { Workbook workbook = writeSheetHolder.getWorkbook(); CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font font = workbook.createFont(); font.setBold(true); headerStyle.setFont(font);
headerStyle.setAlignment(HorizontalAlignment.CENTER); headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(headerStyle); } } }
|
使用方式:在写入时注册钩子
1 2 3 4
| EasyExcel.write("表头样式定制.xlsx", UserInfo.class) .registerWriteHandler(new HeaderStyleHandler()) .sheet("用户表") .doWrite(dataList);
|
案例 2:奇数行背景色高亮(使用 RowWriteHandler)
需求:表格中奇数行背景色设为淡蓝色,增强可读性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import com.alibaba.excel.write.handler.RowWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*;
public class OddRowHighlightHandler implements RowWriteHandler {
@Override public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) { if (Boolean.TRUE.equals(isHead)) { return; }
int rowIndex = row.getRowNum(); if (rowIndex % 2 == 1) { Workbook workbook = writeSheetHolder.getWorkbook(); CellStyle style = workbook.createCellStyle(); style.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
for (Cell cell : row) { cell.setCellStyle(style); } } } }
|
使用方式:
1 2 3 4
| EasyExcel.write("奇数行高亮.xlsx", OrderInfo.class) .registerWriteHandler(new OddRowHighlightHandler()) .sheet("订单表") .doWrite(orderList);
|
案例 3:动态设置 Sheet 列宽(使用 SheetWriteHandler)
需求:根据 Sheet 名称自动调整列宽(如 “用户表” 列宽 20,“订单表” 列宽 25)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import com.alibaba.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
public class DynamicColumnWidthHandler implements SheetWriteHandler {
@Override public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { String sheetName = writeSheetHolder.getWriteSheet().getSheetName(); Sheet sheet = writeSheetHolder.getSheet();
if ("用户表".equals(sheetName)) { sheet.setDefaultColumnWidth(20); } else if ("订单表".equals(sheetName)) { sheet.setDefaultColumnWidth(25); } else { sheet.setDefaultColumnWidth(15); } } }
|
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12
| ExcelWriter writer = EasyExcel.write("动态列宽.xlsx").build();
WriteSheet userSheet = EasyExcel.writerSheet(0, "用户表").head(UserInfo.class).build(); writer.write(userList, userSheet);
WriteSheet orderSheet = EasyExcel.writerSheet(1, "订单表").head(OrderInfo.class).build(); writer.write(orderList, orderSheet);
writer.finish();
|
钩子的注册与执行顺序
注册方式
- 全局注册:通过
EasyExcel.write().registerWriteHandler(handler)
注册,作用于所有 Sheet;
- 局部注册:通过
WriteSheet.writerSheet().registerWriteHandler(handler)
注册,仅作用于当前 Sheet。
1 2 3 4 5
| EasyExcel.write() .registerWriteHandler(new GlobalHandler()) .sheet().registerWriteHandler(new SheetSpecificHandler());
|
执行顺序
- 同类型钩子(如多个
CellWriteHandler
)按注册顺序执行;
- 不同类型钩子按生命周期顺序执行(Workbook → Sheet → Row → Cell)。
注意事项
- 性能考量:钩子会在每个节点触发,复杂逻辑可能影响性能,建议钩子内代码简洁高效;
- POI 版本兼容:钩子中使用 POI 原生 API(如
CellStyle
、Font
)时,需注意与 EasyExcel 依赖的 POI 版本匹配;
- 资源释放:在钩子中创建的 POI 资源(如
Font
、CellStyle
)无需手动释放,EasyExcel 会在 Workbook 关闭时统一处理;
- 调试技巧:通过
relativeRowIndex
(相对行索引,从 0 开始)区分表头行与数据行,isHead
判断是否为表头。
v1.3.10