- 后端
【免费下载链接】fesod
Fast. Easy. Done. Processing spreadsheets without worrying about large files causing OOM.
Apache Fesod(Incubating)是专注于大数据量电子表格处理的开源框架,其核心入口FesodSheet提供了一套流式、低内存占用的读写 API。本篇技术指南围绕 Fesod 的POJO 写入主题展开,覆盖三类高频实战场景:按参数动态导出指定列、精确控制列的写入顺序、以及不创建实体类的无模型(No Model)导出。读完本文,你将掌握excludeColumnFieldNames/includeColumnFieldNames/orderByIncludeColumn/@ExcelProperty(index)等核心参数与注解的用法与底层语义,并能在自己的导出接口中直接落地。
文中所有示例均可在仓库中找到对应源码佐证:核心写入口位于 FesodSheet.java,写参数定义于 WriteBasicParameter.java,列筛选与重排的底层实现在 ClassUtils.java,配套测试见 ExcludeOrIncludeDataTest.java。
准备工作:FesodSheet.write()与 POJO 约定
开始之前,先理解写入口的构建链。FesodSheet提供了write(File)、write(String pathName)、write(OutputStream)三个重载,以及各自带Class head的版本,最终都返回ExcelWriterBuilder,由 FesodSheet.java 中file(...).headIfNotNull(...)完成初始化:
FesodSheet.write(fileName, DemoData.class) // 指定输出文件与模型 Class .sheet() // 进入 sheet 级配置 .doWrite(data()); // 写入数据列表POJO 模型通过@ExcelProperty注解声明表头与列位置。以 Simple Writing 章节中的DemoData为例,其字段为string、date、doubleData:
@Getter @Setter @EqualsAndHashCode public class DemoData { @ExcelProperty("String Title") private String string; @ExcelProperty("Date Title") private Date date; @ExcelProperty("Number Title") private Double doubleData; @ExcelIgnore private String ignore; // 被忽略的字段 }@ExcelProperty注解由 ExcelProperty.java 定义,包含四个核心属性:
| 属性 | 默认值 | 说明 |
|---|---|---|
value() | {""} | 表头名称,可传多个值实现多级表头(写入时自动合并,读取时取最后一个) |
index() | -1 | 列的绝对位置,从 0 开始;为-1时按 Java 类字段声明顺序排序 |
order() | Integer.MAX_VALUE | 列排序权重 |
converter() | AutoConverter.class | 强制该字段使用指定类型转换器 |
需要特别留意注解注释中的优先级约定:index > order > 默认排序,即显式声明了index的列,其位置以index为准。
根据参数动态导出指定列
实际业务中,"导出全部列" 往往不够用:接口可能要求按用户勾选动态决定输出哪些列。Fesod 提供两种互补的集合参数,且集合中装的是POJO 字段名(field name),而不是表头标题。
排除指定列:excludeColumnFieldNames
除集合中列出的字段外,其余字段全部写出:
@Test public void excludeColumnWrite() { String fileName = "excludeColumnFieldWrite" + System.currentTimeMillis() + ".xlsx"; Set<String> excludeColumns = Collections.singleton("date"); FesodSheet.write(fileName, DemoData.class) .excludeColumnFieldNames(excludeColumns) .sheet() .doWrite(data()); }结果:date列消失,string与doubleData两列保留:
| A | B | |
|---|---|---|
| 1 | String Title | Number Title |
| 2 | String0 | 0.56 |
| 3 | String1 | 0.56 |
| 4 | String2 | 0.56 |
| ⋮ | … | … |
| 11 | String9 | 0.56 |
仅导出指定列:includeColumnFieldNames
只写出集合中列出的字段:
@Test public void includeColumnWrite() { String fileName = "includeColumnFieldWrite" + System.currentTimeMillis() + ".xlsx"; Set<String> includeColumns = Collections.singleton("date"); FesodSheet.write(fileName, DemoData.class) .includeColumnFieldNames(includeColumns) .sheet() .doWrite(data()); }结果:仅保留date一列:
| A | |
|---|---|
| 1 | Date Title |
| 2 | 2026-07-31 20:50:23 |
| ⋮ | … |
| 11 | 2026-07-31 20:50:23 |
底层原理:字段过滤发生在头部构建阶段
这两个参数并不是写入时临时裁剪数据,而是在模型头部(Head)构建阶段就完成了字段的取舍。以 ClassUtils.java 的doDeclaredFields为例:
- 通过
FieldUtils.resolveAllFields(clazz)解析出类的全部字段,剔除@ExcelIgnore标注的字段; - 判断写参数中是否存在
excludeColumnFieldNames/excludeColumnIndexes/includeColumnFieldNames/includeColumnIndexes任一配置; - 若命中
writeHolder.ignore(fieldName, index),该字段被加入忽略集并从有序字段图中移除; - 剩余字段重新分配连续的列索引,得到最终
FieldCache。
注意:exclude与include两套参数中,WriteBasicParameter(见 WriteBasicParameter.java)同时定义了基于列索引的excludeColumnIndexes/includeColumnIndexes与基于字段名的两个集合版本,可按需混用。Builder 上同时存在excludeColumnFiledNames(拼写错误)与excludeColumnFieldNames两个方法,前者已标记@Deprecated,参见 AbstractExcelWriterParameterBuilder.java,新代码请统一使用excludeColumnFieldNames/includeColumnFieldNames。
测试 ExcludeOrIncludeDataTest.java 的excludeFieldName用例验证了该行为:排除column1、column3、column4后,回读结果仅剩column2一列。
指定列顺序写入
默认情况下,POJO 模型各列按字段声明顺序依次排列。当需要精细控制列位置时,Fesod 提供了注解与参数两种手段。
方式一:@ExcelProperty(index = n)指定绝对列位
@Getter @Setter @EqualsAndHashCode public class IndexData { @ExcelProperty(value = "String Title", index = 0) private String string; @ExcelProperty(value = "Date Title", index = 1) private Date date; @ExcelProperty(value = "Number Title", index = 3) private Double doubleData; }写入时无需任何额外参数:
@Test public void indexWrite() { String fileName = "indexWrite" + System.currentTimeMillis() + ".xlsx"; FesodSheet.write(fileName, IndexData.class) .sheet() .doWrite(data()); }结果:string在第 0 列、date在第 1 列、doubleData在第 3 列:
| A | B | C | D | |
|---|---|---|---|---|
| 1 | String Title | Date Title | (空) | Number Title |
| 2 | String0 | 2026-07-31 20:50:23 | (空) | 0.56 |
重要语义提醒:
index是从 0 开始的绝对列位置,而不是排序键。上面IndexData声明了0、1、3,所以第 2 列(C 列)刻意留空,且这个空位会原样保留在输出文件中。若希望三列紧挨着排,应依次编号0、1、2。
从源码看,index的解析遵循 ExcelProperty.java 中声明的优先级:index > order > 默认排序;index为-1时回退到类字段顺序。
方式二:includeColumnFieldNames的组合顺序
当与includeColumnFieldNames配合使用时,需要注意一个反直觉的默认行为:列的输出顺序是 POJO 字段的声明顺序,而不是集合的遍历顺序。即使集合把doubleData写在string前面,输出时string依然排第一:
@Test public void includeColumnOrderWrite() { String fileName = "includeColumnFieldWrite" + System.currentTimeMillis() + ".xlsx"; Set<String> includeColumns = new LinkedHashSet<>(Arrays.asList("doubleData", "string")); FesodSheet.write(fileName, DemoData.class) .includeColumnFieldNames(includeColumns) .sheet() .doWrite(data()); }结果:string在前、doubleData在后(与DemoData字段声明顺序一致,而非集合顺序):
| A | B | |
|---|---|---|
| 1 | String Title | Number Title |
| 2 | String0 | 0.56 |
方式三:orderByIncludeColumn(true)按集合顺序输出
若希望列顺序与传入集合的顺序完全一致,追加.orderByIncludeColumn(true)即可:
@Test public void orderByIncludeColumnWrite() { String fileName = "includeColumnFieldWrite" + System.currentTimeMillis() + ".xlsx"; Set<String> includeColumns = new LinkedHashSet<>(Arrays.asList("doubleData", "string")); FesodSheet.write(fileName, DemoData.class) .includeColumnFieldNames(includeColumns) .orderByIncludeColumn(true) .sheet() .doWrite(data()); }结果:doubleData反超string,列顺序变为集合顺序:
| A | B | |
|---|---|---|
| 1 | Number Title | String Title |
| 2 | 0.56 | String0 |
关键前提:orderByIncludeColumn依赖集合具备稳定的迭代顺序,请使用LinkedHashSet或List,不要使用HashSet——后者的迭代顺序不确定,会导致列顺序随机化。
底层原理:resortField的重排逻辑
orderByIncludeColumn的语义由 ClassUtils.java 的resortField实现,其注释明确:该方法仅在includeColumnFieldNames/includeColumnIndexes有值且orderByIncludeColumn为true时生效:
- 当配置的是字段名集合时,遍历集合为每个字段名分配递增的临时索引(
filedIndexMap),随后重建sortedFieldMap:集合中第 N 个字段落在第 N 列; - 当配置的是索引集合时,同理将原始列索引映射为连续的临时索引;
- 若重排后字段的新位置与旧位置不同,旧的
indexFieldMap条目会被移除,即注解指定的顺序在此场景下失效,以集合顺序为准。
测试 ExcludeOrIncludeDataTest.java 的includeFieldNameOrder用例验证:集合按column4、column2、column3传入后,回读的列顺序恰为column4 → column2 → column3,与声明顺序无关。
不创建对象的无模型写入
如果数据来自数据库动态查询、列结构在运行期才能确定,此时不必强行设计实体类,可以用List<List<String>>定义表头、List<List<Object>>定义数据,直接写入。
代码示例
@Test public void noModelWrite() { String fileName = "noModelWrite" + System.currentTimeMillis() + ".xlsx"; FesodSheet.write(fileName) .head(head()) // 动态表头 .sheet("Write Without Object") .doWrite(dataList()); } private List<List<String>> head() { return Arrays.asList( Collections.singletonList("String Title"), Collections.singletonList("Number Title"), Collections.singletonList("Date Title")); } private List<List<Object>> dataList() { List<List<Object>> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(Arrays.asList("String" + i, 0.56, new Date())); } return list; }结果:三列表头 + 十行数据:
| A | B | C | |
|---|---|---|---|
| 1 | String Title | Number Title | Date Title |
| 2 | String0 | 0.56 | 2026-07-31 20:50:23 |
| 3 | String1 | 0.56 | 2026-07-31 20:50:23 |
| ⋮ | … | … | … |
| 11 | String9 | 0.56 | 2026-07-31 20:50:23 |
要点说明:
head()返回List<List<String>>,内层 List 代表一行表头:内层只有一个元素即单级表头;若内层含多个元素,则构成多级(合并)表头,配合automaticMergeHead(默认true,见 ExcelWriterBuilder.java 与 AbstractExcelWriterParameterBuilder.java)自动合并单元格;FesodSheet.write(fileName)不传模型 Class 时,通过.head(head())手动注入表头;- 数据行的每个元素会走内置的类型转换器(字符串原样写出、
Double输出为数值单元格、Date按默认日期格式写出),因此dataList使用List<List<Object>>容纳异构类型; .sheet("Write Without Object")指定了 sheet 名称,等价于sheet(null, sheetName)重载。
小结
| 需求 | 推荐手段 |
|---|---|
| 动态剔除若干列 | excludeColumnFieldNames(Collection<String>),集合装字段名 |
| 动态仅保留若干列 | includeColumnFieldNames(Collection<String>),集合装字段名 |
| 固定精确的列位置 | @ExcelProperty(value = "...", index = n),index从 0 起为绝对列位,支持留空列 |
| 让列顺序跟随传入集合 | includeColumnFieldNames(...).orderByIncludeColumn(true),配合LinkedHashSet/List |
| 不建实体类直接导出 | FesodSheet.write(path).head(List<List<String>>).sheet().doWrite(List<List<Object>>) |
这三类能力覆盖了从"固定模型导出"到"运行时动态导出"的完整谱系:字段级过滤让导出接口可以按用户勾选裁剪列,index与orderByIncludeColumn让列顺序可控、可解释,无模型写入则彻底解耦了实体类设计与动态列场景。若要继续深入,可进一步阅读 Simple Writing 了解基础写入全貌,或在 write 目录下查看表头、格式、样式等进阶章节;对读取端 POJO 映射感兴趣的话,可参考 POJO 读取。
- 后端
【免费下载链接】fesod
Fast. Easy. Done. Processing spreadsheets without worrying about large files causing OOM.
相关推荐
Apache Flink 数据类型与序列化机制完全指南:TypeInformation、POJO 判定与 Kryo/Avro 序列化实战
Apache Flink 数据类型与序列化机制完全指南:TypeInformation、POJO 判定与 Kryo/Avro 序列化实战 Apache Flin
后端大数据流处理批处理BenchmarkDotNet 参数列排序:使用 [Params] 的 Priority 属性控制结果表列顺序
BenchmarkDotNet 参数列排序:使用 Params 的 Priority 属性控制结果表列顺序 导读 在 BenchmarkDotNet 中,当基准
性能测试开发工具antd Table 列顺序控制:用 `Table.EXPAND_COLUMN` 与 `Table.SELECTION_COLUMN` 精确定位展开列与选择列
antd Table 列顺序控制:用 Table.EXPAND_COLUMN 与 Table.SELECTION_COLUMN 精确定位展开列与选择列 导读 在
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考