EasyExcel钩子

EasyExcel钩子(Handler)机制:深度定制 Excel 写入过程

EasyExcel 作为高效的 Excel 处理框架,不仅提供了简单的读写 API,还通过钩子机制(Handler) 允许开发者在 Excel 写入的关键节点(如 Workbook 创建、Sheet 初始化、行 / 单元格生成等)插入自定义逻辑。本文将详细解析 EasyExcel 中的四大钩子接口(Workbook、Sheet、Row、Cell),并通过实例演示如何利用钩子实现复杂需求(如自定义样式、数据校验、动态调整结构等)。

钩子机制的核心作用

钩子机制(Handler)是 EasyExcel 提供的扩展点,允许开发者在 Excel 写入的生命周期节点中嵌入自定义代码,实现以下功能:

  • 自定义样式(如表头加粗、奇数行变色、特定单元格高亮);
  • 数据校验(如单元格值超出范围时报错或自动修正);
  • 动态调整结构(如根据数据内容新增列、合并单元格);
  • 资源管理(如在 Workbook 关闭后释放临时资源)。

EasyExcel 将写入过程划分为四个层级的生命周期,对应四类钩子接口,层级关系如下:

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)

需求:表头文字加粗、背景色设为浅灰色,对齐方式居中。

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) {  
        // 仅处理表头单元格(isHead 为 true)  
        if (Boolean.TRUE.equals(isHead)) {  
            Workbook workbook = writeSheetHolder.getWorkbook();  
            CellStyle headerStyle = workbook.createCellStyle();  

            // 1. 设置背景色(浅灰色)  
            headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());  
            headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);  

            // 2. 文字加粗  
            Font font = workbook.createFont();  
            font.setBold(true);  
            headerStyle.setFont(font);  

            // 3. 水平和垂直居中  
            headerStyle.setAlignment(HorizontalAlignment.CENTER);  
            headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);  

            // 应用样式到表头单元格  
            cell.setCellStyle(headerStyle);  
        }  
    }  
}

使用方式:在写入时注册钩子

EasyExcel.write("表头样式定制.xlsx", UserInfo.class)  
        .registerWriteHandler(new HeaderStyleHandler())  // 注册单元格钩子  
        .sheet("用户表")  
        .doWrite(dataList);

案例 2:奇数行背景色高亮(使用 RowWriteHandler)

需求:表格中奇数行背景色设为淡蓝色,增强可读性。

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) {  
        // 跳过表头(isHead 为 true 时)  
        if (Boolean.TRUE.equals(isHead)) {  
            return;  
        }  

        // 行索引从 0 开始,奇数行(1、3、5...)高亮  
        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);  
            }  
        }  
    }  
}

使用方式

EasyExcel.write("奇数行高亮.xlsx", OrderInfo.class)  
        .registerWriteHandler(new OddRowHighlightHandler())  // 注册行钩子  
        .sheet("订单表")  
        .doWrite(orderList);

案例 3:动态设置 Sheet 列宽(使用 SheetWriteHandler)

需求:根据 Sheet 名称自动调整列宽(如 “用户表” 列宽 20,“订单表” 列宽 25)。

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();  

        // 根据 Sheet 名称设置默认列宽(1 单位 = 1/256 字符宽度)  
        if ("用户表".equals(sheetName)) {  
            sheet.setDefaultColumnWidth(20);  // 列宽 20  
        } else if ("订单表".equals(sheetName)) {  
            sheet.setDefaultColumnWidth(25);  // 列宽 25  
        } else {  
            sheet.setDefaultColumnWidth(15);  // 默认列宽 15  
        }  
    }  
}

使用方式

// 多 Sheet 写入时,钩子会作用于每个 Sheet  
ExcelWriter writer = EasyExcel.write("动态列宽.xlsx").build();  

// 第一个 Sheet:用户表  
WriteSheet userSheet = EasyExcel.writerSheet(0, "用户表").head(UserInfo.class).build();  
writer.write(userList, userSheet);  

// 第二个 Sheet:订单表  
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。
// 全局钩子(作用于所有 Sheet)  
EasyExcel.write()  
    .registerWriteHandler(new GlobalHandler())  
    // 局部钩子(仅作用于当前 Sheet)  
    .sheet().registerWriteHandler(new SheetSpecificHandler());

执行顺序

  • 同类型钩子(如多个 CellWriteHandler)按注册顺序执行;
  • 不同类型钩子按生命周期顺序执行(Workbook → Sheet → Row → Cell)。

注意事项

  1. 性能考量:钩子会在每个节点触发,复杂逻辑可能影响性能,建议钩子内代码简洁高效;
  2. POI 版本兼容:钩子中使用 POI 原生 API(如 CellStyleFont)时,需注意与 EasyExcel 依赖的 POI 版本匹配;
  3. 资源释放:在钩子中创建的 POI 资源(如 FontCellStyle)无需手动释放,EasyExcel 会在 Workbook 关闭时统一处理;
  4. 调试技巧:通过 relativeRowIndex(相对行索引,从 0 开始)区分表头行与数据行,isHead 判断是否为表头。