導出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的技術能夠了解: