EasyExcel之SheetWriteHandler:解锁Excel写入的高阶玩法

引言

在 EasyExcel 强大的功能体系中,SheetWriteHandler 接口是一个关键的组成部分。它允许开发者在写入 Excel 的 Sheet 时进行自定义处理,为实现各种复杂的业务需求提供了强大的支持。通过深入了解和运用 SheetWriteHandler 接口,我们能够更加灵活地控制 Excel 的写入过程,实现诸如设置复杂样式、动态合并单元格、添加数据验证等功能,从而满足多样化的业务场景。接下来,让我们一起深入探索 EasyExcel 中 SheetWriteHandler 接口的常用使用业务场景吧。

一、SheetWriteHandler 接口简介

1.1 接口定义与作用

在 EasyExcel 框架中,SheetWriteHandler 接口是实现对 Excel 写入过程中 Sheet 级别的自定义操作的关键接口。它位于整个 EasyExcel 体系的核心位置,是开发者进行复杂 Excel 文件生成和处理的重要工具。该接口允许开发者在创建 Sheet、写入 Sheet 前后等关键节点介入,对 Sheet 的各种属性和内容进行自定义设置,从而满足多样化的业务需求。

在实际开发中,很多场景下默认的 Excel 生成方式无法满足复杂的业务需求。如在财务报表导出时,需要对 Sheet 进行特定的样式设置,包括字体、字号、颜色、背景色、边框等,以突出显示重要数据和区分不同的数据区域;在数据统计报表中,可能需要根据数据的不同对某些单元格或区域进行合并,使报表结构更加清晰直观。这些复杂的需求都可以通过 SheetWriteHandler 接口来实现。通过实现该接口,开发者能够更加灵活地控制 Excel 文件的生成过程,提高数据展示的质量和用户体验。

1.2 核心方法剖析

SheetWriteHandler 接口主要包含四个核心方法,它们在 Excel 写入过程的不同阶段发挥着重要作用。

  • beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder):这个方法在 Sheet 创建之前被调用。在该方法中,开发者可以进行一些初始化的操作,如设置一些全局的 Sheet 属性,虽然这种情况相对较少。假设我们要在创建 Sheet 前,根据业务需求决定是否启用某个特定的 Sheet 功能,就可以在这个方法中进行判断和设置。例如,如果业务要求某些特殊用户导出的 Excel 中,Sheet 要支持特定的打印设置,我们可以在这个方法中根据用户标识来进行相应的打印设置初始化 。
  • afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder):在 Sheet 创建完成后,此方法被触发。这是一个非常常用的方法,很多与 Sheet 初始化相关的操作都可以在此完成。比如设置 Sheet 的默认列宽、行高,添加自定义的表头样式,或者进行单元格的合并操作等。在一个学生成绩管理系统中,导出学生成绩报表时,我们可以在这个方法中合并第一行的单元格,用于显示报表的总标题,使报表更加美观和规范。
  • beforeSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder):该方法在开始写入 Sheet 数据之前执行。此时,我们可以对即将写入的数据进行一些预处理操作,或者设置一些与写入数据相关的属性。在一个电商订单数据导出场景中,我们可以在这个方法中根据订单的状态对数据进行筛选,只写入符合特定状态(如已完成订单)的数据,同时设置写入数据的格式,确保数据以正确的格式展示在 Excel 中。
  • afterSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder):当 Sheet 数据写入完成后,这个方法被调用。在这个阶段,我们可以进行一些收尾工作,如添加数据统计信息、插入图表或者对整个 Sheet 进行最后的格式调整等。比如在一个销售数据报表导出后,我们可以在这个方法中计算销售总额、平均值等统计数据,并将其添加到 Sheet 的指定位置,同时对整个 Sheet 进行格式优化,使其更符合阅读习惯 。

二、常用业务场景解析

2.1 数据验证与下拉框设置

2.1.1 简单固定下拉框实现

在许多数据录入场景中,为了确保数据的准确性和一致性,常常需要使用下拉框来限制用户的输入选项。利用 SheetWriteHandler 接口,我们可以轻松实现简单固定下拉框的设置。

以员工性别录入为例,假设我们在导出员工信息表格时,希望 "性别" 列只能选择 "男" 或 "女"。首先,我们需要创建一个实现 SheetWriteHandler 接口的类,如GenderDropDownHandler。在afterSheetCreate方法中,我们进行下拉框的设置操作。

java 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import org.apache.poi.ss.usermodel.*;

import org.apache.poi.ss.util.CellRangeAddressList;

public class GenderDropDownHandler implements SheetWriteHandler {

    @Override

    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        // 创建数据验证辅助工具

        DataValidationHelper helper = sheet.getDataValidationHelper();

        // 设置下拉框数据

        String[] genderOptions = {
            "男",
            "女"
        };

        // 创建数据验证约束,这里使用显式列表约束,将下拉框选项作为约束条件

        DataValidationConstraint constraint = helper.createExplicitListConstraint(genderOptions);

        // 设置下拉框的范围,这里从第二行(索引为1)开始,到第100行,列索引为1(假设性别列在第二列)

        CellRangeAddressList addressList = new CellRangeAddressList(1, 100, 1, 1);

        // 创建数据验证对象,将约束和范围关联起来

        DataValidation dataValidation = helper.createValidation(constraint, addressList);

        // 将数据验证添加到Sheet中,这样就完成了下拉框的设置

        sheet.addValidationData(dataValidation);

    }

}

在上述代码中,afterSheetCreate方法在 Sheet 创建完成后被调用。首先获取工作簿和 Sheet 对象,然后通过DataValidationHelper创建数据验证约束,将固定的性别选项作为约束条件。接着定义下拉框的作用范围,即从第二行到第 100 行的第二列。最后创建数据验证对象并添加到 Sheet 中,从而实现了 "性别" 列的简单固定下拉框设置。

在导出 Excel 时,注册这个处理器即可:

java 复制代码
String fileName = "employee_info.xlsx";

List < Employee > employeeList = getEmployeeList(); //获取员工信息列表

EasyExcel.write(fileName, Employee.class)

    .registerWriteHandler(new GenderDropDownHandler())

    .sheet("员工信息")

    .doWrite(employeeList);

通过这种方式,生成的 Excel 文件中 "性别" 列就会显示为下拉框,用户只能从 "男""女" 两个选项中选择,有效避免了性别录入错误。

2.1.2 动态下拉框实现

在实际业务中,下拉框的选项往往不是固定不变的,而是需要根据业务需求从数据库或其他动态源获取。比如在一个电商系统中,导出商品信息表格时,"商品分类" 列的下拉框选项需要实时从数据库中查询获取。

假设我们有一个CategoryService用于从数据库中获取商品分类信息,实现动态下拉框的步骤如下:

首先,创建一个实现 SheetWriteHandler 接口的类,如DynamicCategoryDropDownHandler。在afterSheetCreate方法中,获取动态的下拉框数据,并进行相应设置。

java 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import org.apache.poi.ss.usermodel.*;

import org.apache.poi.ss.util.CellRangeAddressList;

public class DynamicCategoryDropDownHandler implements SheetWriteHandler {

    private CategoryService categoryService;

    public DynamicCategoryDropDownHandler(CategoryService categoryService) {

        this.categoryService = categoryService;

    }

    @Override

    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        DataValidationHelper helper = sheet.getDataValidationHelper();

        // 从数据库获取商品分类数据

        List < String > categoryList = categoryService.getCategories();

        String[] categoryArray = categoryList.toArray(new String[0]);

        DataValidationConstraint constraint = helper.createExplicitListConstraint(categoryArray);

        // 设置下拉框范围,假设商品分类列在第三列,从第二行开始到第200行

        CellRangeAddressList addressList = new CellRangeAddressList(1, 200, 2, 2);

        DataValidation dataValidation = helper.createValidation(constraint, addressList);

        sheet.addValidationData(dataValidation);

    }

}

在上述代码中,DynamicCategoryDropDownHandler类的构造函数接收一个CategoryService实例,用于获取动态的商品分类数据。在afterSheetCreate方法中,首先从CategoryService获取商品分类列表,将其转换为数组后创建数据验证约束。然后定义下拉框的作用范围,最后创建数据验证对象并添加到 Sheet 中。

在导出 Excel 时,同样注册这个处理器:

java 复制代码
String fileName = "product_info.xlsx";

List < Product > productList = getProductList(); //获取商品信息列表

CategoryService categoryService = new CategoryService(); //假设这是获取商品分类服务的实例化

EasyExcel.write(fileName, Product.class)

    .registerWriteHandler(new DynamicCategoryDropDownHandler(categoryService))

    .sheet("商品信息")

    .doWrite(productList);

通过这种方式,生成的 Excel 文件中 "商品分类" 列的下拉框选项会根据数据库中的实时数据动态变化,满足了业务的动态需求。

2.1.3 级联下拉框实现

级联下拉框在复杂的数据录入场景中非常常见,比如省市级联选择。下面以导出地区信息表格时实现省市级联下拉框为例,给出完整实现代码和详细解释。

首先,我们需要准备省级和市级的数据,并创建一个隐藏的 Sheet 来存储它们之间的映射关系。假设我们有两个列表provinceList和cityList分别存储省份和城市信息,且cityList中每个城市对象包含所属省份的标识。

java 复制代码
import com.alibaba.excel.EasyExcel;

import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import org.apache.poi.ss.usermodel.*;

import org.apache.poi.ss.util.CellRangeAddressList;

import java.util.*;

public class ProvinceCityCascadeHandler implements SheetWriteHandler {

    private List < String > provinceList;

    private List < City > cityList;

    public ProvinceCityCascadeHandler(List < String > provinceList, List < City > cityList) {

        this.provinceList = provinceList;

        this.cityList = cityList;

    }

    @Override

    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet mainSheet = writeSheetHolder.getSheet();

        // 创建隐藏Sheet存储省-市映射关系

        Sheet hiddenSheet = workbook.createSheet("hidden_province_city");

        workbook.setSheetHidden(workbook.getSheetIndex(hiddenSheet), true);

        // 在隐藏Sheet中写入省份数据,从第一行第一列开始

        writeDataToSheet(hiddenSheet, provinceList, 0, 0);

        // 构建省-市映射关系并写入隐藏Sheet,从第一行第二列开始

        Map < String, List < String >> provinceCityMap = buildProvinceCityMap();

        int rowIndex = 0;

        for (String province: provinceList) {

            List < String > cities = provinceCityMap.get(province);

            writeDataToSheet(hiddenSheet, cities, rowIndex, 1);

            rowIndex += cities.size();

        }

        // 设置省份下拉框

        DataValidationHelper mainHelper = mainSheet.getDataValidationHelper();

        DataValidationConstraint provinceConstraint = mainHelper.createExplicitListConstraint(provinceList.toArray(new String[0]));

        CellRangeAddressList provinceAddressList = new CellRangeAddressList(1, 100, 0, 0);

        DataValidation provinceValidation = mainHelper.createValidation(provinceConstraint, provinceAddressList);

        mainSheet.addValidationData(provinceValidation);

        // 设置市级下拉框

        for (int i = 1; i <= 100; i++) {

            CellRangeAddressList cityAddressList = new CellRangeAddressList(i, i, 1, 1);

            String formula = "INDIRECT(\"hidden_province_city!$B$" + (findProvinceRowIndex(provinceList, mainSheet.getRow(i).getCell(0).getStringCellValue()) + 1) + ":$B$" + (findProvinceRowIndex(provinceList, mainSheet.getRow(i).getCell(0).getStringCellValue()) + getCityCount(provinceCityMap, mainSheet.getRow(i).getCell(0).getStringCellValue())) + "\")";

            DataValidationConstraint cityConstraint = mainHelper.createFormulaListConstraint(formula);

            DataValidation cityValidation = mainHelper.createValidation(cityConstraint, cityAddressList);

            mainSheet.addValidationData(cityValidation);

        }

    }

    // 将数据写入指定Sheet的指定位置

    private void writeDataToSheet(Sheet sheet, List < String > dataList, int startRow, int startCol) {

        for (int i = 0; i < dataList.size(); i++) {

            Row row = sheet.getRow(startRow + i);

            if (row == null) {

                row = sheet.createRow(startRow + i);

            }

            Cell cell = row.createCell(startCol);

            cell.setCellValue(dataList.get(i));

        }

    }

    // 构建省-市映射关系

    private Map < String, List < String >> buildProvinceCityMap() {

        Map < String, List < String >> provinceCityMap = new HashMap < > ();

        for (City city: cityList) {

            if (!provinceCityMap.containsKey(city.getProvince())) {

                provinceCityMap.put(city.getProvince(), new ArrayList < > ());

            }

            provinceCityMap.get(city.getProvince()).add(city.getName());

        }

        return provinceCityMap;

    }

    // 根据省份名称查找在隐藏Sheet中的行索引

    private int findProvinceRowIndex(List < String > provinceList, String province) {

        return provinceList.indexOf(province);

    }

    // 获取某个省份对应的城市数量

    private int getCityCount(Map < String, List < String >> provinceCityMap, String province) {

        return provinceCityMap.getOrDefault(province, Collections.emptyList()).size();

    }

    // 城市类,包含省份和城市名称

    public static class City {

        private String province;

        private String name;

        public City(String province, String name) {

            this.province = province;

            this.name = name;

        }

        public String getProvince() {

            return province;

        }

        public String getName() {

            return name;

        }

    }

}

在上述代码中,ProvinceCityCascadeHandler类实现了 SheetWriteHandler 接口。在afterSheetCreate方法中,首先创建一个隐藏的 Sheet 用于存储省 - 市映射关系,并将省份和对应的城市数据写入该 Sheet。然后设置省份下拉框,将所有省份作为固定选项。对于市级下拉框,通过INDIRECT函数根据选择的省份动态获取对应的城市列表作为下拉选项。writeDataToSheet方法用于将数据写入指定 Sheet 的指定位置,buildProvinceCityMap方法构建省 - 市映射关系,findProvinceRowIndex方法查找省份在隐藏 Sheet 中的行索引,getCityCount方法获取某个省份对应的城市数量。

在导出 Excel 时,注册这个处理器:

java 复制代码
String fileName = "area_info.xlsx";

List < Area > areaList = getAreaList(); //获取地区信息列表

List < String > provinceList = getProvinceList(); //获取省份列表

List < ProvinceCityCascadeHandler.City > cityList = getCityList(); //获取城市列表

EasyExcel.write(fileName, Area.class)

    .registerWriteHandler(new ProvinceCityCascadeHandler(provinceList, cityList))

    .sheet("地区信息")

    .doWrite(areaList);

通过以上实现,生成的 Excel 文件中 "省份" 和 "城市" 列形成了级联下拉框,用户选择省份后,城市下拉框会动态显示该省份对应的城市选项,大大提高了数据录入的准确性和便捷性。

2.2 样式与格式设置

2.2.1 表头样式定制

在导出 Excel 报表时,为了使报表更加美观和易读,常常需要对表头进行样式定制。利用 SheetWriteHandler 接口,我们可以在 Sheet 创建前后轻松设置表头的字体、背景色、对齐方式等样式。

以一个学生成绩报表为例,假设我们希望表头字体为黑体、加粗,背景色为浅蓝色,水平和垂直居中对齐。创建一个实现 SheetWriteHandler 接口的类,如HeaderStyleHandler,在afterSheetCreate方法中进行表头样式设置。

java 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import org.apache.poi.ss.usermodel.*;

public class HeaderStyleHandler implements SheetWriteHandler {

    @Override

    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        // 创建表头样式对象

        CellStyle headerStyle = workbook.createCellStyle();

        // 设置水平居中对齐

        headerStyle.setAlignment(HorizontalAlignment.CENTER);

        // 设置垂直居中对齐

        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 设置背景色为浅蓝色

        headerStyle.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());

        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        // 创建字体对象并设置为黑体、加粗

        Font headerFont = workbook.createFont();

        headerFont.setFontName("黑体");

        headerFont.setBold(true);

        headerStyle.setFont(headerFont);

        // 获取表头行,假设表头在第一行

        Row headerRow = sheet.getRow(0);

        if (headerRow == null) {

            headerRow = sheet.createRow(0);

        }

        // 遍历表头行的单元格,应用表头样式

        for (int i = 0; i < headerRow.getLastCellNum(); i++) {

            Cell cell = headerRow.getCell(i);

            if (cell == null) {

                cell = headerRow.createCell(i);

            }

            cell.setCellStyle(headerStyle);

        }

    }

}

在上述代码中,afterSheetCreate方法在 Sheet 创建完成后被调用。首先获取工作簿和 Sheet 对象,然后创建表头样式对象headerStyle,设置其对齐方式、背景色和字体等属性。接着获取表头行,如果表头行不存在则创建。最后遍历表头行的单元格,为每个单元格应用表头样式。

在导出 Excel 时,注册这个处理器:

java 复制代码
String fileName = "student_score.xlsx";

List < StudentScore > studentScoreList = getStudentScoreList(); //获取学生成绩列表

EasyExcel.write(fileName, StudentScore.class)

    .registerWriteHandler(new HeaderStyleHandler())

    .sheet("学生成绩")

    .doWrite(studentScoreList);

通过这种方式,生成的 Excel 文件表头将呈现出我们定制的样式,使报表更加清晰美观,提升了用户体验。

2.2.2 数据行样式设置

除了表头样式,根据数据内容动态设置数据行样式也是常见的业务需求。比如在一个销售报表中,我们希望根据销售额的高低为数据行设置不同的背景色,以突出显示重要数据。

假设我们有一个Sale类表示销售数据,包含amount(销售额)字段。创建一个实现 SheetWriteHandler 接口的类,如DataRowStyleHandler,在beforeSheetWrite方法中根据数据内容设置数据行样式。

java 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import com.alibaba.excel.write.metadata.holder.WriteTableHolder;

import org.apache.poi.ss.usermodel.*;

public class DataRowStyleHandler implements SheetWriteHandler {

    @Override

    public void beforeSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) {

            Workbook workbook = writeWorkbookHolder.getWorkbook();

            Sheet sheet = writeSheetHolder.getSheet();

            // 创建高销售额行样式对象

            CellStyle highAmountStyle = workbook.createCellStyle();

            highAmountStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());

            highAmountStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

// 创建低销售额行样式对象

## 三、实际案例应用

### 3.1 电商订单数据导出

在电商系统中,订单数据的导出是一项常见且重要的任务。假设我们有一个电商订单数据导出的场景,需要将订单数据导出到Excel表格中,并且希望在导出的表格中设置一些特殊的功能。具体需求如下:

- 在"订单状态"列设置下拉框,下拉选项为"待付款""待发货""已发货""已完成""已取消",方便用户查看和筛选不同状态的订单。

- 将"订单金额"列的数据格式设置为货币格式,保留两位小数,以符合财务数据的展示规范。

- 对于"订单备注"列中包含特定关键词(如"加急")的订单,添加特殊批注,提醒相关人员注意。

首先,创建一个实现SheetWriteHandler接口的类,如`OrderExportHandler`。在这个类中,我们实现上述三个需求。

java 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import com.alibaba.excel.write.metadata.holder.WriteTableHolder;

import org.apache.poi.ss.usermodel.*;

import org.apache.poi.ss.util.CellRangeAddressList;

public class OrderExportHandler implements SheetWriteHandler {

    @Override

    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        // 设置订单状态下拉框

        DataValidationHelper helper = sheet.getDataValidationHelper();

        String[] statusOptions = {
            "待付款",
            "待发货",
            "已发货",
            "已完成",
            "已取消"
        };

        DataValidationConstraint statusConstraint = helper.createExplicitListConstraint(statusOptions);

        CellRangeAddressList statusAddressList = new CellRangeAddressList(1, 1000, 2, 2); // 假设订单状态列在第三列,从第二行开始到第1000行

        DataValidation statusValidation = helper.createValidation(statusConstraint, statusAddressList);

        sheet.addValidationData(statusValidation);

        // 设置订单金额列格式为货币格式

        CellStyle amountStyle = workbook.createCellStyle();

        CreationHelper creationHelper = workbook.getCreationHelper();

        amountStyle.setDataFormat(creationHelper.createDataFormat().getFormat("¥#,##0.00"));

        for (int i = 1; i <= 1000; i++) {

            Row row = sheet.getRow(i);

            if (row == null) {

                row = sheet.createRow(i);

            }

            Cell amountCell = row.getCell(3); // 假设订单金额列在第四列

            if (amountCell == null) {

                amountCell = row.createCell(3);

            }

            amountCell.setCellStyle(amountStyle);

        }

    }

    @Override

    public void afterSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        // 为包含"加急"关键词的订单备注添加批注

        for (int i = 1; i <= 1000; i++) {

            Row row = sheet.getRow(i);

            if (row == null) {

                continue;

            }

            Cell remarkCell = row.getCell(4); // 假设订单备注列在第五列

            if (remarkCell != null && remarkCell.getCellType() == CellType.STRING && remarkCell.getStringCellValue().contains("加急")) {

                Drawing < ? > drawing = sheet.createDrawingPatriarch();

                ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, (short) 4, i, (short) 6, i + 1);

                Comment comment = drawing.createCellComment(anchor);

                comment.setString(new XSSFRichTextString("此订单为加急订单,请优先处理"));

                remarkCell.setCellComment(comment);

            }

        }

    }

}

在上述代码中,afterSheetCreate方法在 Sheet 创建完成后被调用,用于设置订单状态下拉框和订单金额列的格式。afterSheetWrite方法在 Sheet 数据写入完成后被调用,用于为包含 "加急" 关键词的订单备注添加批注。

在导出 Excel 时,注册这个处理器:

java 复制代码
String fileName = "order_export.xlsx";

List < Order > orderList = getOrderList(); //获取订单信息列表

EasyExcel.write(fileName, Order.class)

    .registerWriteHandler(new OrderExportHandler())

    .sheet("订单数据")

    .doWrite(orderList);

通过上述实现,生成的 Excel 文件中,"订单状态" 列会显示为下拉框,方便用户筛选订单;"订单金额" 列会以货币格式展示,更直观地呈现金额数据;对于包含 "加急" 关键词的订单备注,会添加特殊批注,提醒相关人员注意。这样的导出表格能够更好地满足电商业务中对订单数据处理和分析的需求。

3.2 员工信息管理系统

在员工信息管理系统中,导出员工信息表格也是一个常见的功能。假设我们需要实现以下功能:

  • 在 "部门" 列设置下拉框,下拉选项从数据库中动态获取,方便用户选择和查看不同部门的员工信息。
  • 将 "员工编号""姓名""部门""职位" 等关键信息列设置为加粗字体,突出显示这些重要信息。
  • 对于 "员工备注" 列中的内容,添加相应的批注,详细说明备注的具体含义。

首先,创建一个实现 SheetWriteHandler 接口的类,如EmployeeExportHandler。假设我们有一个DepartmentService用于从数据库中获取部门信息。

java 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;

import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

import com.alibaba.excel.write.metadata.holder.WriteTableHolder;

import org.apache.poi.ss.usermodel.*;

import org.apache.poi.ss.util.CellRangeAddressList;

public class EmployeeExportHandler implements SheetWriteHandler {

    private DepartmentService departmentService;

    public EmployeeExportHandler(DepartmentService departmentService) {

        this.departmentService = departmentService;

    }

    @Override

    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        // 设置部门下拉框

        DataValidationHelper helper = sheet.getDataValidationHelper();

        List < String > departmentList = departmentService.getDepartments();

        String[] departmentArray = departmentList.toArray(new String[0]);

        DataValidationConstraint departmentConstraint = helper.createExplicitListConstraint(departmentArray);

        CellRangeAddressList departmentAddressList = new CellRangeAddressList(1, 500, 2, 2); // 假设部门列在第三列,从第二行开始到第500行

        DataValidation departmentValidation = helper.createValidation(departmentConstraint, departmentAddressList);

        sheet.addValidationData(departmentValidation);

        // 设置关键信息列字体为加粗

        CellStyle keyInfoStyle = workbook.createCellStyle();

        Font boldFont = workbook.createFont();

        boldFont.setBold(true);

        keyInfoStyle.setFont(boldFont);

        int[] keyColumns = {
            0,
            1,
            2,
            3
        }; // 假设员工编号、姓名、部门、职位分别在第1、2、3、4列

        for (int col: keyColumns) {

            for (int i = 0; i <= 500; i++) {

                Row row = sheet.getRow(i);

                if (row == null) {

                    row = sheet.createRow(i);

                }

                Cell cell = row.getCell(col);

                if (cell == null) {

                    cell = row.createCell(col);

                }

                cell.setCellStyle(keyInfoStyle);

            }

        }

    }

    @Override

    public void afterSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) {

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        Sheet sheet = writeSheetHolder.getSheet();

        // 为员工备注添加批注

        for (int i = 1; i <= 500; i++) {

            Row row = sheet.getRow(i);

            if (row == null) {

                continue;

            }

            Cell remarkCell = row.getCell(5); // 假设员工备注列在第六列

            if (remarkCell != null && remarkCell.getCellType() == CellType.STRING) {

                Drawing < ? > drawing = sheet.createDrawingPatriarch();

                ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, (short) 5, i, (short) 7, i + 1);

                Comment comment = drawing.createCellComment(anchor);

                comment.setString(new XSSFRichTextString("备注内容:" + remarkCell.getStringCellValue()));

                remarkCell.setCellComment(comment);

            }

        }

    }

}

在上述代码中,afterSheetCreate方法用于设置部门下拉框和关键信息列的加粗字体样式。afterSheetWrite方法用于为员工备注添加批注,详细说明备注内容。

在导出 Excel 时,注册这个处理器:

java 复制代码
String fileName = "employee_export.xlsx";

List < Employee > employeeList = getEmployeeList(); //获取员工信息列表

DepartmentService departmentService = new DepartmentService(); //假设这是获取部门信息服务的实例化

EasyExcel.write(fileName, Employee.class)

    .registerWriteHandler(new EmployeeExportHandler(departmentService))

    .sheet("员工信息")

    .doWrite(employeeList);

通过以上实现,生成的 Excel 文件中,"部门" 列会显示为动态下拉框,方便用户选择和查看不同部门的员工信息;关键信息列会以加粗字体突出显示,便于用户快速识别重要信息;"员工备注" 列会添加详细的批注,帮助用户更好地理解备注内容。这样的导出表格能够提高员工信息管理系统的使用效率和数据展示效果。

四、使用注意事项与优化建议

4.1 性能优化

在使用 SheetWriteHandler 进行大量数据导出时,性能问题不容忽视。频繁创建对象会占用较多的内存资源,影响系统的整体性能。在设置样式时,如果每次都创建新的CellStyle和Font对象,当数据量较大时,会导致内存消耗急剧增加。为了减少不必要的对象创建,可以在类的成员变量中创建并复用这些对象。

文件 I/O 操作也是影响性能的关键因素。频繁的磁盘写入操作会降低导出速度,特别是在处理大数据量时。可以采用分批写入的方式,将数据分批次写入 Excel 文件,而不是一次性写入。这样可以减少 I/O 操作的次数,提高导出效率。在导出电商订单数据时,如果订单数据量很大,可以每次读取 1000 条订单数据写入 Excel,完成一批后再写入下一批 。

4.2 兼容性问题

不同版本的 EasyExcel 中,SheetWriteHandler 接口及相关方法可能存在兼容性差异。在某些版本中,afterSheetCreate方法的参数类型或行为可能发生了变化,如果不注意版本更新说明,直接在新版本中使用旧版本的代码,可能会导致编译错误或运行时异常。因此,在升级 EasyExcel 版本时,一定要仔细阅读版本更新说明,了解接口和方法的变化情况。如果遇到兼容性问题,可以参考官方文档或社区论坛,寻找解决方案。有时候可能需要对代码进行相应的调整,以适应新版本的要求 。

4.3 常见错误与解决方法

在使用 SheetWriteHandler 过程中,可能会遇到一些常见错误。当下拉框数据不显示时,可能是因为数据验证的设置不正确,如下拉框的范围设置错误、数据验证约束的创建方式有误等。此时,需要仔细检查数据验证的相关代码,确保范围和约束设置正确。如果样式设置无效,可能是因为样式对象没有正确应用到单元格上,或者样式设置的顺序有误。在设置样式时,要确保先创建样式对象,然后将其应用到相应的单元格上,并且注意样式设置的先后顺序,避免后面的设置覆盖了前面的设置 。

五、总结与展望

5.1 回顾 SheetWriteHandler 的强大功能

通过对 SheetWriteHandler 接口在数据验证与下拉框设置、样式与格式设置以及实际案例中的应用探讨,我们充分领略了其在处理复杂 Excel 写入需求时的强大能力。从简单固定下拉框到动态下拉框和级联下拉框的实现,为数据录入提供了准确且便捷的方式;表头和数据行样式的定制,使 Excel 报表更加美观易读;在电商订单数据导出和员工信息管理系统等实际案例中,进一步验证了其在满足多样化业务需求方面的有效性,能够帮助我们生成符合各种业务场景需求的高质量 Excel 文件。

5.2 对未来应用拓展的思考

随着业务的不断发展和数据处理需求的日益复杂,SheetWriteHandler 接口在未来有着更广阔的应用拓展空间。在大数据分析领域,结合海量数据的处理需求,我们可以进一步优化其性能,实现更高效的数据写入和复杂报表生成。在多语言支持方面,根据不同地区和用户的语言习惯,动态设置 Excel 的表头、批注等内容为相应语言,提升国际化应用水平。鼓励读者在实际开发中,不断探索和尝试新的应用场景,挖掘 SheetWriteHandler 接口的更多潜力,为解决复杂的业务问题提供创新的思路和方法 。

复制代码
相关推荐
悟空码字1 天前
Spring Boot 整合 MongoDB 最佳实践:CRUD、分页、事务、索引全覆盖
java·spring boot·后端
皮皮林5513 天前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602735 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840826 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解6 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解6 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记6 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者7 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840827 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解7 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端