写在前面
导出 Excel 是系统中经常用到的功能。实现的方案也很多,可以自己去封装 Apache Poi,也可以直接使用别人已经封装好的类库。如果需求简单的话,自己做实现也是可以的,所有的 bug 和 feature 都将是可控的。使用第三方的类库主要是方便,避免重复造轮子,但不好地方在于如果发现 bug 或者 feature 不满足时,会严重受限于类库版本的迭代。
在导出数据中经常会含有时间,在时间格式化时,如果不指定时区,则会使用服务器的时区进行格式化,这样可能导致导出的时间不是希望的时间。因而指定时区是一个很重要的功能。由于 EasyExcel 没有提供指定时区的功能,因而需要自己进行解决。
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.2</version></dependency>
复制代码
实现方案
EasyExcel 提供的可拓展功能为用户提供了很大的方便,特别是允许自定义转化器和监听器。这里将使用自定义转化器的功能进行解决。因为一个表中的时间一般都是在同一个时区,所以应该实现全局时区,同时应该支持动态配置,而不是硬编码一个时区到代码中。此外,这里还提供了一个设置类 Date 类型属性的时区的方法。
如下为最终的效果:
// io.gitlab.donespeak.tutorial.excel.easyexcel.timezone.DateTimeZoneConverterTest.TheDate@Getter@Setter@ToString@EqualsAndHashCode@NoArgsConstructorpublic static class TheDate { @DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS") @ExcelProperty(index = 0) private Date date;
@DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS") @DateTimeZone("Asia/Tokyo") @ExcelProperty(index = 1) private Date jpDate;}
复制代码
这里推荐使用registerConverter方法直接替代ExcelWriterBuilder和ExcelReaderBuilder中的默认的类型转化器。虽然也可以通过指定ExcelProperty.converter的方法进行配置,但还是会稍显麻烦。
// 用 US/Central 去写入Excel中的时间EasyExcel.write(file, TheDate.class) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)) .sheet("theDate").doWrite(listOriginal);
// 用 US/Central 去读取Excel中的时间List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)) .head(TheDate.class).sheet().doReadSync();
复制代码
定义 @DateTimeZone 注解
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface DateTimeZone {
/** * Specific value reference {@link TimeZone#getAvailableIDs()} */ String value() default "";}
复制代码
该注解指定一个 Date 属性的时区。
实现时区转化器:Date <-> String
import com.alibaba.excel.converters.date.DateStringConverter;import com.alibaba.excel.metadata.CellData;import com.alibaba.excel.metadata.GlobalConfiguration;import com.alibaba.excel.metadata.property.ExcelContentProperty;
import java.text.ParseException;import java.util.Date;
public class DateTimeZoneStringConverter extends DateStringConverter {
private final String globalTimeZoneId;
public DateTimeZoneStringConverter() { super(); globalTimeZoneId = null; }
public DateTimeZoneStringConverter(String timeZoneId) { super(); globalTimeZoneId = timeZoneId; }
@Override public Date convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws ParseException {
String timeZoneId = getTimeZoneId(contentProperty); String timeFormat = getTimeFormat(contentProperty);
// System.out.println(String.format("%s: %s: %s", cellData.getStringValue(), timeFormat, timeZoneId)); Date date = DateUtils.parseDate(cellData.getStringValue(), timeFormat , timeZoneId); return date; }
@Override public CellData convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
String timeZoneId = getTimeZoneId(contentProperty); String timeFormat = getTimeFormat(contentProperty);
// System.out.println(String.format("%s: %s: %s", value, timeFormat, timeZoneId)); String excelValue = DateUtils.format(value, timeFormat, timeZoneId); return new CellData(excelValue); }
private String getTimeZoneId(ExcelContentProperty contentProperty) { if (contentProperty == null) { return null; } return DateTimeZoneUtil.getTimeZone(contentProperty.getField(), globalTimeZoneId); }
private String getTimeFormat(ExcelContentProperty contentProperty) { if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { return null; } return contentProperty.getDateTimeFormatProperty().getFormat(); }}
复制代码
com.alibaba.excel.converters.date.DateStringConverter 是 EasyExcel 定义的用于将Date导出为String的转化器。此外还有将Date转化为Number的转化器com.alibaba.excel.converters.date.DateNumberConverter。
为了方便,DateTimeZoneStringConverter直接继承了DateStringConverter,并覆盖用于转化的两个方法convertToJavaData() 和 convertToExcelData()。看起来修改了很多,实际上没有太大的改动,就增加了一个获取时区的方法和在SimpleDateFormat中增加了TimeZone。
这里的DateUtils是重写的 DateUtils,EasyExcel 中的com.alibaba.excel.util.DateUtils的实现没有支持 TimeZone。
import com.alibaba.excel.util.StringUtils;
import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.TimeZone;
public class DateUtils {
public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss"; public static final String DATE_FORMAT_19_FORWARD_SLASH = "yyyy/MM/dd HH:mm:ss"; private static final String MINUS = "-";
private DateUtils() { throw new AssertionError("DateUtils can't be instantiated."); }
/** * convert string to date */ public static Date parseDate(String dateString, String dateFormat, String timeZone) throws ParseException { if (StringUtils.isEmpty(dateFormat)) { dateFormat = switchDateFormat(dateString); } SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); if(!StringUtils.isEmpty(timeZone)) { sdf.setTimeZone(TimeZone.getTimeZone(timeZone)); } return sdf.parse(dateString); }
/** * convert string to date */ public static Date parseDate(String dateString) throws ParseException { return parseDate(dateString, switchDateFormat(dateString), null); }
/** * switch date format */ private static String switchDateFormat(String dateString) { int length = dateString.length(); switch (length) { case 19: if (dateString.contains(MINUS)) { return DATE_FORMAT_19; } else { return DATE_FORMAT_19_FORWARD_SLASH; } case 17: return DATE_FORMAT_17; case 14: return DATE_FORMAT_14; case 10: return DATE_FORMAT_10; default: throw new IllegalArgumentException("can not find date format for:" + dateString); } }
/** * Format date * <p> * yyyy-MM-dd HH:mm:ss */ public static String format(Date date, String timeZone) { return format(date, null, timeZone); }
/** * Format date * * 当dateFormat为空时,默认使用 yyyy-MM-dd HH:mm:ss */ public static String format(Date date, String dateFormat, String timeZone) { if (date == null) { return ""; } if (StringUtils.isEmpty(dateFormat)) { dateFormat = DATE_FORMAT_19; } SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); if(!StringUtils.isEmpty(timeZone)) { sdf.setTimeZone(TimeZone.getTimeZone(timeZone)); } return sdf.format(date); }}
复制代码
单独封装了 TimeZone 获取的方法。
import com.alibaba.excel.util.StringUtils;import java.lang.reflect.Field;
public class DateTimeZoneUtil {
public static String getTimeZone(Field field, String defaultTimeZoneId) { DateTimeZone dateTimeZone = field.getAnnotation(DateTimeZone.class); if (dateTimeZone == null) { // 如果Field没有DateTimeZone注解,则使用全局的 return defaultTimeZoneId; } String timeZoneId = dateTimeZone.value(); if (StringUtils.isEmpty(timeZoneId)) { // 如果Field的DateTimeZone注解的值为空,则使用全局的 return defaultTimeZoneId; } return timeZoneId; }}
复制代码
实现时区转化器:Date <-> Number
import com.alibaba.excel.converters.date.DateNumberConverter;import com.alibaba.excel.metadata.CellData;import com.alibaba.excel.metadata.GlobalConfiguration;import com.alibaba.excel.metadata.property.ExcelContentProperty;import lombok.extern.slf4j.Slf4j;import org.apache.poi.ss.usermodel.DateUtil;
import java.math.BigDecimal;import java.util.Calendar;import java.util.Date;import java.util.TimeZone;
@Slf4jpublic class DateTimeZoneNumberConverter extends DateNumberConverter {
private final String globalTimeZoneId;
public DateTimeZoneNumberConverter() { this(null); }
public DateTimeZoneNumberConverter(String timeZoneId) { super(); this.globalTimeZoneId = timeZoneId; }
@Override public Date convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
TimeZone timeZone = getTimeZone(contentProperty); boolean use1904windowing = getUse1904windowing(contentProperty, globalConfiguration);
return DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), use1904windowing, timeZone); }
@Override public CellData convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
TimeZone timeZone = getTimeZone(contentProperty); Calendar calendar = getCalendar(value, timeZone);
boolean use1904windowing = getUse1904windowing(contentProperty, globalConfiguration);
CellData cellData = new CellData(BigDecimal.valueOf(DateUtil.getExcelDate(calendar, use1904windowing)));
return cellData; }
private TimeZone getTimeZone(ExcelContentProperty contentProperty) { if(contentProperty == null) { return null; } String timeZoneId = DateTimeZoneUtil.getTimeZone(contentProperty.getField(), globalTimeZoneId); return TimeZone.getTimeZone(timeZoneId); }
private Calendar getCalendar(Date date, TimeZone timeZone) { Calendar calStart = Calendar.getInstance(); calStart.setTime(date); if(timeZone != null) { calStart.setTimeZone(timeZone); }
return calStart; }
private boolean getUse1904windowing(ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { return contentProperty.getDateTimeFormatProperty().getUse1904windowing(); } else { return globalConfiguration.getUse1904windowing(); } }}
复制代码
类似DateTimeZoneStringConverter,DateTimeZoneNumberConverter继承了DateNumberConverter,提供了一个Date与Numbe之间转化的转化器。
测试
如下的单元测试,对@DateTimeZone,DateTimeZoneStringConverter和DateTimeZoneNumberConverter均进行了测试。同时这也是一个完整的使用案例。
package io.gitlab.donespeak.tutorial.excel.easyexcel.timezone;
import com.alibaba.excel.EasyExcel;import com.alibaba.excel.annotation.ExcelProperty;import com.alibaba.excel.annotation.format.DateTimeFormat;import lombok.EqualsAndHashCode;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import lombok.ToString;import org.junit.Rule;import org.junit.Test;import org.junit.rules.TemporaryFolder;
import java.io.File;import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.TimeZone;import java.util.function.Function;import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
/** * @author DoneSpeak * @date 2019/11/21 22:01 */public class DateTimeZoneConverterTest { @Getter @Setter @ToString @EqualsAndHashCode @NoArgsConstructor public static class TheDate { @DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS") @ExcelProperty(index = 0) private Date date;
@DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS") @DateTimeZone("Asia/Tokyo") @ExcelProperty(index = 1) private Date jpDate; }
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
/** * https://www.zeitverschiebung.net/cn/all-time-zones.html */ private static final String TIME_ZONE_ID_US_CENTRAL = "US/Central"; private static final String TIME_ZONE_ID_ETC_UTC = "Etc/UTC"; private static final String TIME_ZONE_ID_JP = "Asia/Tokyo"; // UTC+9
public File getTestDirectory() { // return new File(""); // 使用本地路径,方便生成的文件 return temporaryFolder.getRoot(); }
@Test public void testDateTimeZoneStringConverter() { File file = new File(getTestDirectory(), "easyexcel-test-dateTimeZoneStringConverter.xlsx");
if(file.exists()) { file.delete(); } List<TheDate> listOriginal = data();
// 用 US/Central 去写入Excel中的时间 EasyExcel.write(file, TheDate.class) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)) .sheet("theDate").doWrite(listOriginal);
// 用 US/Central 去读取Excel中的时间 List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)) .head(TheDate.class).sheet().doReadSync();
assertListEquals(listOriginal, listUsCentralWriteUsCentralRead);
// 用 UTC 时区去读取Excel中的时间 List<TheDate> listUsCentralWriteEtcUtcRead = EasyExcel.read(file) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_ETC_UTC)) .head(TheDate.class).sheet().doReadSync();
System.out.println(listUsCentralWriteEtcUtcRead);
assertTimeSpan(collectDate(listOriginal, d -> d.getDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getDate()), TIME_ZONE_ID_US_CENTRAL, TIME_ZONE_ID_ETC_UTC); assertTimeSpan(collectDate(listOriginal, d -> d.getJpDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getJpDate()), TIME_ZONE_ID_JP, TIME_ZONE_ID_JP); }
@Test public void testDateTimeZoneNumberConverter() { File file = new File(getTestDirectory(), "easyexcel-test-dateTimeZoneNumberConverter.xlsx");
if(file.exists()) { file.delete(); } List<TheDate> listOriginal = data();
// 用 US/Central 去写入Excel中的时间 EasyExcel.write(file, TheDate.class) .registerConverter(new DateTimeZoneNumberConverter(TIME_ZONE_ID_US_CENTRAL)) .sheet("theDate").doWrite(listOriginal);
// 用 US/Central 去读取Excel中的时间 List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file) .registerConverter(new DateTimeZoneNumberConverter(TIME_ZONE_ID_US_CENTRAL)) .head(TheDate.class).sheet().doReadSync();
assertListEquals(listOriginal, listUsCentralWriteUsCentralRead);
// 用 UTC 时区去读取Excel中的时间 List<TheDate> listUsCentralWriteEtcUtcRead = EasyExcel.read(file) .registerConverter(new DateTimeZoneNumberConverter(TIME_ZONE_ID_ETC_UTC)) .head(TheDate.class).sheet().doReadSync();
assertTimeSpan(collectDate(listOriginal, d -> d.getDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getDate()), TIME_ZONE_ID_US_CENTRAL, TIME_ZONE_ID_ETC_UTC); assertTimeSpan(collectDate(listOriginal, d -> d.getJpDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getJpDate()), TIME_ZONE_ID_JP, TIME_ZONE_ID_JP); }
private List<TheDate> data() { Date now = getTime();
List<TheDate> datas = new ArrayList<>();
TheDate thd = new TheDate(); thd.setDate(now); thd.setJpDate(now);
datas.add(thd); return datas; }
private Date getTime() { // 这里的时间保留保留位数应该和@DateTimeFormat一致,否则值比较时将会不相等 return new Date(); }
private long getTimeSpan(Date from, Date to) { return from.getTime() - to.getTime(); } private long getTimeZoneTimeSpan(String timeZoneIdfrom, String timeZoneIdTo) { return TimeZone.getTimeZone(timeZoneIdfrom).getRawOffset() - TimeZone.getTimeZone(timeZoneIdTo).getRawOffset(); }
private void assertListEquals(List<TheDate> listOriginal, List<TheDate> listUsCentral) { assertEquals(listOriginal.size(), listUsCentral.size()); for(int i = 0; i < listOriginal.size(); i ++) { TheDate original = listOriginal.get(i); TheDate usCentral = listUsCentral.get(i); assertEquals(original, usCentral); } }
private void assertTimeSpan(List<Date> dateOriginal, List<Date> dateOperated, String timeZoneWrite, String timeZoneRead) {
long timeZoneSpanFromUsCentralToEtcUtc = getTimeZoneTimeSpan(timeZoneWrite, timeZoneRead);
for(int i = 0; i < dateOriginal.size(); i ++) { // 对于同一个时间字符串,A时区 - B时区 = B时区解释 - A时区解释 long span = getTimeSpan(dateOperated.get(i), dateOriginal.get(i)); assertEquals(timeZoneSpanFromUsCentralToEtcUtc, span); } }
private List<Date> collectDate( final List<TheDate> list, Function<TheDate, Date> function) { return list.stream().map(function).collect(Collectors.toList()); }}
复制代码
拓展 - 聊聊 EasyExcel 的转化器
写过程
EasyExcel.write(file, TheDate.class) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)) .sheet("theDate").doWrite(listOriginal);
复制代码
EasyExcel.write(file, TheDate.class): 会创建一个 ExcelWriterBuilder,目前也仅仅是设置了文件输出路径和表头格式。
registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)): 为 ExcelWriterBuilder.writeWorkbook添加自定义转化器。
sheet("theDate"): 创建ExcelWriterSheetBuilder,并配置ExcelWriter的上下文,也就是转化器等信息。
.doWrite(listOriginal): ExcelWriter将列表生成 excel 文件。
转化器的配置就发生在sheet("theDate")中。按照:
ExcelWriterSheetBuilder.sheet() -> ExcelWriterSheetBuilder.build() -> new ExcelWriter(writeWorkbook) -> new ExcelBuilderImpl(writeWorkbook) -> new WriteContextImpl(writeWorkbook) -> WirteContextImpl.initCurrentSheetHolder(writeSheet) -> new WriteSheetHolder(writeSheet, writeWorkbookHolder) -> new AbstractWriteHolder()
复制代码
到这里就可以找到配置 Converter 的代码了:
// 配置默认Converterif (parentAbstractWriteHolder == null) { setConverterMap(DefaultConverterLoader.loadDefaultWriteConverter());} else { setConverterMap(new HashMap<String, Converter>(parentAbstractWriteHolder.getConverterMap()));}// 配置自定义Conveterif (writeBasicParameter.getCustomConverterList() != null && !writeBasicParameter.getCustomConverterList().isEmpty()) { for (Converter converter : writeBasicParameter.getCustomConverterList()) { getConverterMap().put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); }}
复制代码
com.alibaba.excel.converters包下有 EasyExcel 提供的默认的 Converter。在配置默认 Converter 的流程中,DefaultConverterLoader.loadDefaultWriteConverter()将默认的转化器进行加载。返回一个以converter.supportJavaTypeKey()构成的 key,converter作为 value 的 Map,加载完成之后会有如下的列表(映射关系中会将基本类型转化为封装类型):
BigDecimal.class: BigDecimalNumberConverterBoolean.class: BooleanBooleanConverterByte.class: ByteNumberConverterDate.class: DateStringConverterDouble.class: DoubleNumberConverterFloat.class: FloatNumberConverterInteger.class: IntegerNumberConverterLong.class: LongNumberConverterShort.class: ShortNumberConverterString.class: StringStringConverterFile.class: FileImageConverterInpurtStream.class: InputStreamImageConverterbyte[].class: ByteArrayImageConverterByte[].class: BoxingByteArrayImageConverterURL.class: UrlImageConverter
复制代码
如果有自定义的 Converter,则会使用自动定义的 Conveter,则会根据supportJavaTypeKey替换原来的默认的 Converter。
在写入的时候,由AbstractExcelWriteExecutor根据数据的类型,获取正确的转化器将 JavaObject 转化为正确的CellData。
读过程
List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file) .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)) .head(TheDate.class).sheet().doReadSync();
复制代码
EasyExcel.read(file): 创建ExcelReaderBuilder对象,配置输入文件位置,默认表头和默认监听器。
registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)): 为ExcelReaderBuilder.readWorkbook添加自定义转化器。
head(TheDate.class): 设置表头。
sheet(): 创建ExcelReaderSheetBuilder,并配置ExcelReader的上下文,也就是转化器等信息。
doReadSync(): 同步读,将数据从文件中读取到对象列表中。
和写过程类似,转化器的配置就发生在sheet()中,且过程基本是一样的。按照:
ExcelReaderSheetBuilder.sheet() -> ExcelReaderSheetBuilder.build() -> new ExcelReader(readWorkbook) -> new ExcelAnalyserImpl(readWorkbook) -> new AnalysisContextImpl(readWorkbook) -> new ReadWorkbookHolder(readWorkbook) -> new AbstractReadHolder()
复制代码
到这里就可以找到配置 Converter 的代码了:
if (parentAbstractReadHolder == null) { setConverterMap(DefaultConverterLoader.loadDefaultReadConverter());} else { setConverterMap(new HashMap<String, Converter>(parentAbstractReadHolder.getConverterMap()));}if (readBasicParameter.getCustomConverterList() != null && !readBasicParameter.getCustomConverterList().isEmpty()) { for (Converter converter : readBasicParameter.getCustomConverterList()) { getConverterMap().put( ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), converter); }}
复制代码
和写过程不同,读过程通过DefaultConverterLoader.loadDefaultReadConverter()加载映射关系,加载之后可以得到由converter.supportJavaTypeKey()和converter.supportExcelTypeKey()构成的 key,以converter为 value 的 map,有如下的映射列表:
BigDecimal.class <- CellDataTypeEnum.BOOLEAN: BigDecimalBooleanConverterBigDecimal.class <- CellDataTypeEnum.NUMBER: BigDecimalNumberConverterBigDecimal.class <- CellDataTypeEnum.STRING: BigDecimalStringConverter
Boolean.class <- CellDataTypeEnum.BOOLEAN: BooleanBooleanConverterBoolean.class <- CellDataTypeEnum.NUMBER: BooleanNumberConverterBoolean.class <- CellDataTypeEnum.STRING: BooleanStringConverter
Byte.class <- CellDataTypeEnum.BOOLEAN: ByteBooleanConverterByte.class <- CellDataTypeEnum.NUMBER: ByteNumberConverterByte.class <- CellDataTypeEnum.STRING: ByteStringConverter
Date.class <- CellDataTypeEnum.NUMBER: DateNumberConverterDate.class <- CellDataTypeEnum.STRING: DateStringConverter
Double.class <- CellDataTypeEnum.BOOLEAN: DoubleBooleanConverterDouble.class <- CellDataTypeEnum.NUMBER: DoubleNumberConverterDouble.class <- CellDataTypeEnum.STRING: DoubleStringConverter
Float.class <- CellDataTypeEnum.BOOLEAN: FloatBooleanConverterFloat.class <- CellDataTypeEnum.NUMBER: FloatNumberConverterFloat.class <- CellDataTypeEnum.STRING: FloatStringConverter
Integer.class <- CellDataTypeEnum.BOOLEAN: IntegerBooleanConverterInteger.class <- CellDataTypeEnum.NUMBER: IntegerNumberConverterInteger.class <- CellDataTypeEnum.STRING: IntegerStringConverter
Long.class <- CellDataTypeEnum.BOOLEAN: LongBooleanConverterLong.class <- CellDataTypeEnum.NUMBER: LongNumberConverterLong.class <- CellDataTypeEnum.STRING: LongStringConverter
Long.class <- CellDataTypeEnum.BOOLEAN: LongBooleanConverterLong.class <- CellDataTypeEnum.NUMBER: LongNumberConverterLong.class <- CellDataTypeEnum.STRING: LongStringConverter
Short.class <- CellDataTypeEnum.BOOLEAN: ShortBooleanConverterShort.class <- CellDataTypeEnum.NUMBER: ShortNumberConverterShort.class <- CellDataTypeEnum.STRING: ShortStringConverter
String.class <- CellDataTypeEnum.BOOLEAN: StringBooleanConverterString.class <- CellDataTypeEnum.NUMBER: StringNumberConverterString.class <- CellDataTypeEnum.STRING: StringStringConverter
String.class <- CellDataTypeEnum.ERROR: StringErrorConverter
复制代码
和写入不同,读具有更多的组合方式,excel 文件中的字段类型可以有多种,对应的 javaObject 的属性也可以有多种。通过这样的映射关系可以确定输入数据的类型要转化为目标数据类型所需要使用到的转化器。
如果有自定义的 Converter,则会使用自动定义的 Conveter,则会根据supportJavaTypeKey和supportExcelTypeKey替换原来的默认的 Converter。
类型转化的使用就得看ReadListener的子类的使用了。
参考和其他
源码见:tutorial/tutorial-excel
该功能已经提出 issue,可以关注:希望为DateTimeFormat增加时区参数 #841
前端生成 Excel 的技术可以了解:
评论