导出Excel是系统中常常用到的功能。实现的方案也不少,能够本身去封装Apache Poi,也能够直接使用别人已经封装好的类库。若是需求简单的话,本身作实现也是能够的,全部的bug和feature都将是可控的。使用第三方的类库主要是方便,避免重复造轮子,但很差地方在于若是发现bug或者feature不知足时,会严重受限于类库版本的迭代。 html
在导出数据中常常会含有时间,在时间格式化时,若是不指定时区,则会使用服务器的时区进行格式化,这样可能致使导出的时间不是但愿的时间。于是指定时区是一个很重要的功能。因为EasyExcel没有提供指定时区的功能,于是须要本身进行解决。前端
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.2</version> </dependency>
EasyExcel提供的可拓展功能为用户提供了很大的方便,特别是容许自定义转化器和监听器。这里将使用自定义转化器的功能进行解决。由于一个表中的时间通常都是在同一个时区,因此应该实现全局时区,同时应该支持动态配置,而不是硬编码一个时区到代码中。此外,这里还提供了一个设置类Date类型属性的时区的方法。 java
以下为最终的效果:git
// io.gitlab.donespeak.tutorial.excel.easyexcel.timezone.DateTimeZoneConverterTest.TheDate @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; }
这里推荐使用registerConverter
方法直接替代ExcelWriterBuilder
和ExcelReaderBuilder
中的默认的类型转化器。虽然也能够经过指定ExcelProperty.converter
的方法进行配置,但仍是会稍显麻烦。github
// 用 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();
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface DateTimeZone { /** * Specific value reference {@link TimeZone#getAvailableIDs()} */ String value() default ""; }
该注解指定一个Date属性的时区。apache
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
。xss
这里的DateUtils
是重写的DateUtils,EasyExcel中的com.alibaba.excel.util.DateUtils
的实现没有支持TimeZone。ide
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获取的方法。gitlab
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; } }
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; @Slf4j public 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.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的代码了:
// 配置默认Converter if (parentAbstractWriteHolder == null) { setConverterMap(DefaultConverterLoader.loadDefaultWriteConverter()); } else { setConverterMap(new HashMap<String, Converter>(parentAbstractWriteHolder.getConverterMap())); } // 配置自定义Conveter if (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: BigDecimalNumberConverter Boolean.class: BooleanBooleanConverter Byte.class: ByteNumberConverter Date.class: DateStringConverter Double.class: DoubleNumberConverter Float.class: FloatNumberConverter Integer.class: IntegerNumberConverter Long.class: LongNumberConverter Short.class: ShortNumberConverter String.class: StringStringConverter File.class: FileImageConverter InpurtStream.class: InputStreamImageConverter byte[].class: ByteArrayImageConverter Byte[].class: BoxingByteArrayImageConverter URL.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: BigDecimalBooleanConverter BigDecimal.class <- CellDataTypeEnum.NUMBER: BigDecimalNumberConverter BigDecimal.class <- CellDataTypeEnum.STRING: BigDecimalStringConverter Boolean.class <- CellDataTypeEnum.BOOLEAN: BooleanBooleanConverter Boolean.class <- CellDataTypeEnum.NUMBER: BooleanNumberConverter Boolean.class <- CellDataTypeEnum.STRING: BooleanStringConverter Byte.class <- CellDataTypeEnum.BOOLEAN: ByteBooleanConverter Byte.class <- CellDataTypeEnum.NUMBER: ByteNumberConverter Byte.class <- CellDataTypeEnum.STRING: ByteStringConverter Date.class <- CellDataTypeEnum.NUMBER: DateNumberConverter Date.class <- CellDataTypeEnum.STRING: DateStringConverter Double.class <- CellDataTypeEnum.BOOLEAN: DoubleBooleanConverter Double.class <- CellDataTypeEnum.NUMBER: DoubleNumberConverter Double.class <- CellDataTypeEnum.STRING: DoubleStringConverter Float.class <- CellDataTypeEnum.BOOLEAN: FloatBooleanConverter Float.class <- CellDataTypeEnum.NUMBER: FloatNumberConverter Float.class <- CellDataTypeEnum.STRING: FloatStringConverter Integer.class <- CellDataTypeEnum.BOOLEAN: IntegerBooleanConverter Integer.class <- CellDataTypeEnum.NUMBER: IntegerNumberConverter Integer.class <- CellDataTypeEnum.STRING: IntegerStringConverter Long.class <- CellDataTypeEnum.BOOLEAN: LongBooleanConverter Long.class <- CellDataTypeEnum.NUMBER: LongNumberConverter Long.class <- CellDataTypeEnum.STRING: LongStringConverter Long.class <- CellDataTypeEnum.BOOLEAN: LongBooleanConverter Long.class <- CellDataTypeEnum.NUMBER: LongNumberConverter Long.class <- CellDataTypeEnum.STRING: LongStringConverter Short.class <- CellDataTypeEnum.BOOLEAN: ShortBooleanConverter Short.class <- CellDataTypeEnum.NUMBER: ShortNumberConverter Short.class <- CellDataTypeEnum.STRING: ShortStringConverter String.class <- CellDataTypeEnum.BOOLEAN: StringBooleanConverter String.class <- CellDataTypeEnum.NUMBER: StringNumberConverter String.class <- CellDataTypeEnum.STRING: StringStringConverter String.class <- CellDataTypeEnum.ERROR: StringErrorConverter
和写入不一样,读具备更多的组合方式,excel文件中的字段类型能够有多种,对应的javaObject的属性也能够有多种。经过这样的映射关系能够肯定输入数据的类型要转化为目标数据类型所须要使用到的转化器。
若是有自定义的Converter,则会使用自动定义的Conveter,则会根据supportJavaTypeKey
和supportExcelTypeKey
替换原来的默认的Converter。
类型转化的使用就得看ReadListener
的子类的使用了。
源码见:tutorial/tutorial-excel
该功能已经提出issue,能够关注:但愿为DateTimeFormat增长时区参数 #841
前端生成Excel的技术能够了解: