- 后端
【免费下载链接】fesod
Fast. Easy. Done. Processing spreadsheets without worrying about large files causing OOM.
导读
本文聚焦 Apache Fesod(Incubating)表格处理框架中的自定义数据转换器(Converter)机制,讲解如何基于Converter<T>接口编写读写双向的自定义转换逻辑,并通过@ExcelProperty(converter = ...)字段级注册与 builder 级.registerConverter(...)全局注册两种方式接入框架,同时结合仓库源码剖析转换器的键(key)构建、默认转换器装载与解析优先级。读完本文,你将能够在 Fesod 中自由定制单元格与 Java 对象之间的数据映射,例如给 String 加上前后缀、自定义时间戳格式化、特殊枚举转换等,而无需修改框架内置逻辑。
一、Converter 是什么:读写双向的数据转换枢纽
在 Apache Fesod 中,Excel 单元格(ReadCellData/WriteCellData)与 Java 对象字段之间的双向转换统一由Converter<T>接口承担:
- 读方向:把 Excel 单元格数据转换为 Java 对象字段值(
convertToJavaData); - 写方向:把 Java 对象字段值转换为 Excel 单元格数据(
convertToExcelData)。
框架已为常见的 Java 类型(String、Integer、Long、BigDecimal、Date、LocalDate、LocalDateTime、LocalTime、布尔、字节、短整型、浮点、图片、URL 等)预置了大量内置转换器,位于 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/ 目录下。当内置转换器无法满足业务需求(例如需要对字符串做前后缀加工、读取原始单元格元数据、按特定规则解析自定义格式)时,即可编写自定义转换器并注册使用。
官方指南 website/docs/sheet/advanced/custom-converter.md 完整阐述了这一机制:自定义转换器既可逐字段注册(通过注解),也可全局注册(通过 builder),并且同时作用于读取与写入两个方向。
二、创建自定义 Converter:接口逐方法拆解
2.1 实现一个最简单的自定义转换器
以下代码(取自官方指南的完整示例)实现了一个给字符串加"Custom: "前缀的转换器:
public class CustomStringStringConverter implements Converter<String> { @Override public Class<?> supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public String convertToJavaData(ReadConverterContext<?> context) { return "Custom: " + context.getReadCellData().getStringValue(); } @Override public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) { return new WriteCellData<>("Custom: " + context.getValue()); } }2.2 接口方法的职责与签名
查看接口源码 Converter.java 可以确认,Converter<T>共定义四个核心方法(均带默认实现,未覆盖时抛出UnsupportedOperationException):
| 方法 | 方向 | 作用 |
|---|---|---|
supportJavaTypeKey() | 元信息 | 声明该转换器支持的 Java 类型(如String.class),用于注册键匹配 |
supportExcelTypeKey() | 元信息 | 声明该转换器支持的 Excel 单元格类型(CellDataTypeEnum 枚举,如STRING、NUMBER、BOOLEAN、DATE等) |
convertToJavaData(ReadConverterContext<?> context) | 读 | 将单元格数据转换为 Java 对象 |
convertToExcelData(WriteConverterContext<T> context) | 写 | 将 Java 对象转换为单元格数据 |
2.3 上下文对象能拿到什么
- 读上下文 ReadConverterContext.java:包含
readCellData(Excel 单元格数据,非空)、contentProperty(字段属性,可为空)和analysisContext(分析上下文,非空,可获取当前读取的全局配置)。 - 写上下文 WriteConverterContext.java:包含
value(Java 数据,非空)、contentProperty(可为空)和writeContext(写入上下文)。
接口还提供了基于(value/cellData, contentProperty, globalConfiguration)三参签名的默认方法,上述基于上下文的便捷方法会在内部自动委托调用它们,因此你只需要覆盖适合自己场景的那一组即可。
注意空值:参考 NullableObjectConverter.java 的注释说明,实现
convertToExcelData时传入的value可能是null(例如某行某列没有值),务必做好空值判断。该接口本身是Converter<T>的空扩展,用于在语义上标记"允许空值"的转换器。
三、两种注册方式:字段级 vs 全局
官方指南用一张表格清晰对比了两种注册方式:
| 方式 | 作用范围 | 如何注册 |
|---|---|---|
| Per-field(字段级) | 仅作用于单个字段 | @ExcelProperty(converter = MyConverter.class) |
| Global(全局) | 作用于所有"Java 类型 + Excel 类型"匹配的字段 | builder 上调用.registerConverter(new MyConverter()) |
3.1 字段级注册(注解方式)
查看 ExcelProperty.java 源码,注解提供converter()属性,类型为Class<? extends Converter<?>>,默认值为AutoConverter.class(即"按类型自动匹配",见 AutoConverter.java,它只是一个空实现标记,实际转换由内置转换器完成):
public class DemoData { // 仅对 name 字段生效:写入时自动加前缀,读取时自动去自定义加工 @ExcelProperty(value = "姓名", converter = CustomStringStringConverter.class) private String name; @ExcelProperty("年龄") private Integer age; }这种方式适合个别字段有特殊格式、而同一类型其他字段保持默认行为的场景。
3.2 全局注册(builder 方式)
在读写 builder 上调用.registerConverter(converter)。该方法的底层实现在 AbstractParameterBuilder.java(约 L122 起),它会将传入的转换器追加到parameter().getCustomConverterList()自定义转换器列表中,后续构建 holder 时再统一合并进转换器 Map。
全局注册适合某一类型整体都需要自定义转换的场景,例如项目中所有Timestamp字段都要按自定义格式输出。测试 CustomConverterTest.java 中的converterMapTest与globalConverterInSheetHolder验证了全局注册后,转换器确实会以(Java 类型, Excel 类型)为键出现在 writer/sheet holder 的converterMap()中。
四、全局注册的完整读写示例
4.1 写入(Write with Global Converter)
官方指南给出的写入示例:
@Test public void customConverterWrite() { String fileName = "customConverterWrite" + System.currentTimeMillis() + ".xlsx"; FesodSheet.write(fileName, DemoData.class) .registerConverter(new CustomStringStringConverter()) .sheet() .doWrite(data()); }4.2 读取(Read with Global Converter)
@Test public void customConverterRead() { String fileName = "path/to/demo.xlsx"; FesodSheet.read(fileName, DemoData.class, new DemoDataListener()) .registerConverter(new CustomStringStringConverter()) .sheet() .doRead(); }4.3 可组合注册多个转换器
.registerConverter支持链式多次调用,同一个转换器列表会依次收集。仓库测试中的writeCsv/writeXls/writeXlsx用例就同时注册了TimestampNumberConverter与TimestampStringConverter两个转换器(见 CustomConverterTest.java L125-L131),并分别输出 CSV、XLS、XLSX 三种格式,说明该机制对 CSV/XLS/XLSX 均生效。
五、转换器解析优先级(Resolution Priority)
官方指南明确给出了三层优先级,从高到低:
- 字段级转换器(
@ExcelProperty(converter = ...))——优先级最高 - builder 级转换器(
.registerConverter(...))——次之 - 内置默认转换器——优先级最低
也就是说,字段级注解会强制该字段使用指定转换器(ExcelProperty源码注释原文为 "Force the current field to use this converter"),即使同一类型已在全局注册了其他转换器,也只对未标注注解的字段生效。
测试 CustomConverterTest.java 中的fieldLevelConverterTakesPrecedenceOverRegisteredConverter用例验证了这一行为:同一条数据里,标注了@ExcelProperty(converter = FieldLevelStringConverter.class)的字段输出为field:value,而仅依赖全局RegisteredStringConverter的字段输出为registered:value,最终 CSV 内容断言为field:value,registered:value,证明字段级转换器确实覆盖了全局注册的同类型转换器。
六、底层原理:转换器键(ConverterKey)与默认装载
6.1 键 = Java 类型 + Excel 单元格类型
全局转换器之所以能"自动匹配所有符合类型的字段",关键在于每个转换器都以(支持类型, 单元格类型)二元组作为唯一键存入 Map。源码 ConverterKeyBuild.java 实现如下:
buildKey(clazz, cellDataTypeEnum)生成ConverterKey(clazz, cellDataTypeEnum);- 内部维护一张装箱映射表(BOXING_MAP),把
int/byte/long/double/float/char/short/boolean等基本类型自动映射为对应的包装类(Integer/Byte/Long/Double/Float/Character/Short/Boolean),因此自定义转换器声明supportJavaTypeKey()返回包装类型即可同时覆盖基本类型字段。
6.2 默认转换器的装载
DefaultConverterLoader.java 在类加载时初始化三组 Map:
- defaultWriteConverter:写方向默认转换器,键仅为 Java 类型(如
BigDecimalNumberConverter、DateDateConverter、FileImageConverter、UrlImageConverter等),另有一批"必须转成字符串"的场景转换器(如DateStringConverter、LocalDateTimeStringConverter等),键为 Java 类型 +STRING单元格类型; - allConverter:全部转换器(读方向默认即使用它,
loadDefaultReadConverter()直接返回loadAllConverter()),统一以(Java 类型, Excel 类型)为键注册,包括每个类型对应的Boolean/Number/String三种转换器变体,以及图片、URL 等专用转换器。
你的自定义转换器通过 builder 注册后,会与这些内置 Map 合并(复制默认 Map 后 put 自定义项),从而在解析字段时优先命中自定义键。
七、内置转换器速览与自定义场景建议
从 DefaultConverterLoader.java 的初始化代码可以整理出框架内置支持的转换器族(均位于converters/下对应子包):
| 目标类型 | 子包 | 常见转换器 |
|---|---|---|
String | string/ | StringStringConverter、StringNumberConverter、StringBooleanConverter、StringErrorConverter、StringImageConverter、StringBase64ImageConverter、StringPathnameImageConverter |
| 数值类型 | integer/ longconverter/ doubleconverter/ floatconverter/ byteconverter/ shortconverter/ | 各类型的Number/Boolean/String三件套 |
| 高精度 | bigdecimal/ biginteger/ | BigDecimalNumberConverter、BigIntegerStringConverter等 |
| 日期时间 | date/ localdate/ localdatetime/ localtime/ | DateDateConverter、LocalDateTimeStringConverter等 |
| 布尔 | booleanconverter/ | BooleanBooleanConverter等 |
| 图片/文件/URL | bytearray/ file/ inputstream/ url/ | FileImageConverter、InputStreamImageConverter、ByteArrayImageConverter、UrlImageConverter(配合 CidrBlock.java 等策略类使用) |
适合编写自定义转换器的典型场景:
- 字符串加工(前后缀、脱敏、编码转换)——如本文示例;
- 自定义日期/时间格式,或按业务规则解析日期字符串(配合 website/docs/sheet/read/converter.md 的转换器主题一起学习);
Timestamp等 SQL 类型字段的读写(参考测试中的TimestampStringConverter/TimestampNumberConverter,见 fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/);- 需要拿到原始单元格类型(如
ReadCellData)做精细判断的场景,测试模型 ConverterReadData.java 展示了直接在字段上声明ReadCellData<?>的用法。
八、实践建议与注意事项
- 覆盖方法按需:只读或只写的场景,可只实现对应方向的
convertToJavaData/convertToExcelData,但元信息方法supportJavaTypeKey()与supportExcelTypeKey()必须如实返回,否则注册键无法匹配。 - 基本类型字段:
supportJavaTypeKey()建议返回包装类(如Integer.class),ConverterKeyBuild会自动处理基本类型的装箱映射。 - 空值处理:写方向实现中显式判断
value == null,参考NullableObjectConverter的语义说明。 - 优先级陷阱:字段级注解是"强制"的,会完全覆盖全局与内置转换器;若某字段只想用默认行为,就不要给它标
converter。 - 全局注册的副作用:
registerConverter影响整个 workbook/sheet 中所有匹配类型的字段,注册前请确认不会误伤其他字段的默认格式。
相关阅读
- 官方自定义转换器指南(本文主体来源):website/docs/sheet/advanced/custom-converter.md
- 读取方向转换器详解:website/docs/sheet/read/converter.md
- 写入方向转换器详解:website/docs/sheet/write/converter.md
- 转换器接口与实现源码:converters/
- 优先级与全局注册验证测试:CustomConverterTest.java
- 后端
【免费下载链接】fesod
Fast. Easy. Done. Processing spreadsheets without worrying about large files causing OOM.
相关推荐
Doctrine ORM 自定义映射类型(Custom Mapping Types)完全指南:从类型创建、注册到字段值转换实战
Doctrine ORM 自定义映射类型(Custom Mapping Types)完全指南:从类型创建、注册到字段值转换实战 Doctrine ORM 允许开
数据库ORM后端深入 Spring Converter 接口:从类型转换原理到自定义转换器实战(spring-reading 源码实践)
深入 Spring Converter 接口:从类型转换原理到自定义转换器实战(spring reading 源码实践) Spring 的类型转换体系是数据绑定
示例工程文档Apache Fesod扩展开发终极指南:如何编写自定义转换器和处理器
Apache Fesod扩展开发终极指南:如何编写自定义转换器和处理器 Apache Fesod是一个快速、简洁、解决大文件内存溢出的Java处理Excel工具
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考