动态导出 Excel 表格 Demo
总结
本文介绍使用 Java 和 EasyExcel 实现动态导出 Excel 表格的需求。包括核心思路、拆解步骤、Demo 测试。
详情
核心思路
核心思想是在写入 Excel 之前,动态地构建表头数据结构,然后将数据转换成与表头顺序一致的列表集合。EasyExcel 提供了灵活的 API 来支持这种操作,不需要依赖固定的 Java 类注解 (@ExcelProperty
)。
拆解步骤
- 定义实体:用来封装所有可能导出的字段。例如,一个
StudentScore
类,包含姓名、考号、分数、合格状态、性别、评价等所有可能的字段。 - 动态生成表头 (Header):
- 表头的本质是
List<List<String>>
结构。可以根据传参决定需要哪些表头,并动态地创建这个列表。- 外层的
List
表示“多行表头”; - 内层的
List<String>
表示“一行中的多个列标题”。
- 外层的
- 例如,一个函数可以接收一个字段列表
["姓名", "分数"]
,然后将其转换为 EasyExcel 需要的List<List<String>>
格式,即[["姓名"], ["分数"]]
。
- 表头的本质是
- 准备动态数据 (Data):
- 数据需要是一个
List<List<Object>>
结构。- 外层
List
的每个元素代表 Excel 中的一行数据。 - 内层
List
的每个元素则对应这一行中各个单元格的数据。
- 外层
- 关键:内层
List
中数据的顺序必须与动态生成的表头顺序严格一致。- 我们需要编写一个转换逻辑,遍历原始的数据对象列表(如
List<StudentScore>
),根据动态表头列表的字段顺序,从每个StudentScore
对象中提取对应的值,并按顺序组装成List<Object>
。
- 我们需要编写一个转换逻辑,遍历原始的数据对象列表(如
- 数据需要是一个
- 使用 EasyExcel 写入:
- 调用
EasyExcel.write()
方法。 - 使用
.head()
方法传入我们动态生成的表头List<List<String>>
。 - 使用
.sheet()
方法指定工作表名称。 - 使用
.doWrite()
方法传入我们准备好的数据List<List<Object>>
。
- 调用
通过以上步骤,可以实现动态导出 Excel 表格的需求。
实践
Demo 测试
添加必要的 Maven 依赖
定义实体类
package com.example.model; import lombok.Builder; import lombok.Data; @Data @Builder public class StudentScore { private String name; // 姓名 private String code; // 考号 private String score; // 分数 }
导出服务
DynamicExcelExportService.java
package com.example.service; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.example.model.StudentScore; import java.io.OutputStream; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class DynamicExcelExportService { /** * 动态导出Excel * * @param outputStream 输出流,可以是 FileOutputStream 或 HttpServletResponse.getOutputStream() * @param requiredHeaders 需要导出的表头字段名列表,顺序即为导出顺序 * @param allData 所有学生的数据 */ public void export(OutputStream outputStream, List<String> requiredHeaders, List<StudentScore> allData) { if (requiredHeaders == null || requiredHeaders.isEmpty()) { throw new IllegalArgumentException("表头不能为空"); } // 1. 动态生成表头 List<List<String>> excelHead = requiredHeaders.stream() .map(headerName -> { List<String> headColumn = new ArrayList<>(); headColumn.add(headerName); return headColumn; }) .collect(Collectors.toList()); // 2. 准备动态数据 List<List<Object>> excelData = new ArrayList<>(); if (allData != null && !allData.isEmpty()) { // 使用 LinkedHashMap 来保证字段顺序与传入的 header 一致 Map<String, String> headerFieldMap = getHeaderFieldMapping(); //外层循环:每个元素代表 Excel 中的一行数据。 for (StudentScore student : allData) { List<Object> dataRow = new ArrayList<>(); //内层循环:每个元素对应一行中各个单元格的数据。 for (String headerName : requiredHeaders) { // 根据表头名找到对应的字段名,然后从对象中取值 String fieldName = headerFieldMap.get(headerName); dataRow.add(getFieldValue(student, fieldName)); } excelData.add(dataRow); } } // 3. 使用 EasyExcel 写入 EasyExcel.write(outputStream) .head(excelHead) // 设置动态表头 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 可选:自适应列宽 .sheet("成绩") // 设置工作表名 .doWrite(excelData); // 写入数据 } /** * 定义表头中文名和实体类字段名的映射关系 * 使用 LinkedHashMap 保证后续遍历时的顺序 * * @return 映射Map */ private Map<String, String> getHeaderFieldMapping() { Map<String, String> map = new LinkedHashMap<>(); map.put("姓名", "name"); map.put("考号", "code"); map.put("分数", "score"); return map; } /** * 使用简化的反射(或if-else)根据字段名获取对象的值 * 注意:在生产环境中,为了性能,可以考虑使用更高效的反射库如 cglib 或 MapStruct * * @param student 对象 * @param fieldName 字段名 * @return 字段值 */ private Object getFieldValue(StudentScore student, String fieldName) { if (fieldName == null) { return ""; } switch (fieldName) { case "name": return student.getName(); case "code": return student.getCode(); case "score": return student.getScore(); default: return ""; // or throw an exception } } }
测试入口:模拟了两次不同场景下需要不同表头的导出请求。
public static void main(String[] args) { DynamicExcelExportService exportService = new DynamicExcelExportService(); List<StudentScore> data = generateMockData(); // 场景一:需要 '姓名', '分数' System.out.println("场景1导出Excel..."); List<String> headers1 = Arrays.asList("姓名", "分数"); try (OutputStream os1 = new FileOutputStream("test1.xlsx")) { exportService.export(os1, headers1, data); System.out.println("考生成绩已成功导出到 test1.xlsx"); } catch (Exception e) { e.printStackTrace(); } System.out.println("\n-----------------------------------\n"); // 场景二:需要 '姓名', '考号', '分数' System.out.println("场景2开始导出Excel..."); List<String> headers2 = Arrays.asList("姓名", "考号", "分数"); try (OutputStream os2 = new FileOutputStream("test2.xlsx")) { exportService.export(os2, headers2, data); System.out.println("考生成绩已成功导出到 test2.xlsx"); } catch (Exception e) { e.printStackTrace(); } } /** * 生成模拟数据 */ private static List<StudentScore> generateMockData() { List<StudentScore> list = new ArrayList<>(); list.add(StuDataDemo.builder().name("张三").code("0001").score("95.5").build()); list.add(StuDataDemo.builder().name("李四").code("0002").score("88.0").build()); list.add(StuDataDemo.builder().name("王五").code("0003").score("59.0").build()); list.add(StuDataDemo.builder().name("赵六").code("0004").score("76.0").build()); return list; }
关联文章
- 错误:lncorrect string value ‘xF0 xA4 x8B xAE’ 排查