EasyExcel介绍
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
网站
- 官方网站:https://easyexcel.opensource.alibaba.com/
- github地址:https://github.com/alibaba/easyexcel
- gitee地址:https://gitee.com/easyexcel/easyexcel
数据导入
demo
@PostMapping(value = "/t3", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void t3(@RequestPart("file") MultipartFile file) throws IOException {
ReadListener<OrderExcel> readListener = new ReadListener<OrderExcel>() {
@Override
public void invoke(OrderExcel orderExcel, AnalysisContext analysisContext) {
System.out.println(orderExcel);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println(analysisContext);
}
};
EasyExcel.read(file.getInputStream(), OrderExcel.class, readListener).sheet().doRead();
}
}
源码解读
EasyExcelFactory#read(InputStream, Class, ReadListener)
public static ExcelReaderBuilder read(InputStream inputStream, Class head, ReadListener readListener) {
ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
excelReaderBuilder.file(inputStream);
if (head != null) {
excelReaderBuilder.head(head);
}
if (readListener != null) {
excelReaderBuilder.registerReadListener(readListener);
}
return excelReaderBuilder;
}
ExcelReaderBuilder#sheet()
,设置sheet。
public ExcelReaderSheetBuilder sheet() {
return this.sheet((Integer)null, (String)null);
}
ExcelReaderSheetBuilder#doRead
,执行读取操作。
public void doRead() {
if (this.excelReader == null) {
throw new ExcelGenerateException("Must use 'EasyExcelFactory.read().sheet()' to call this method");
} else {
this.excelReader.read(new ReadSheet[]{this.build()});
this.excelReader.finish();
}
}
ExcelReader#read(ReadSheet...)
,读取。
public ExcelReader read(ReadSheet... readSheet) {
return this.read(Arrays.asList(readSheet));
}
public ExcelReader read(List<ReadSheet> readSheetList) {
this.excelAnalyser.analysis(readSheetList, Boolean.FALSE);
return this;
}
ExcelAnalyserImpl#analysis
,调用 this.excelReadExecutor.execute();
public void analysis(List<ReadSheet> readSheetList, Boolean readAll) {
try {
if (!readAll && CollectionUtils.isEmpty(readSheetList)) {
throw new IllegalArgumentException("Specify at least one read sheet.");
} else {
this.analysisContext.readWorkbookHolder().setParameterSheetDataList(readSheetList);
this.analysisContext.readWorkbookHolder().setReadAll(readAll);
try {
this.excelReadExecutor.execute();
} catch (ExcelAnalysisStopException var4) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Custom stop!");
}
}
}
} catch (RuntimeException var5) {
this.finish();
throw var5;
} catch (Throwable var6) {
this.finish();
throw new ExcelAnalysisException(var6);
}
}
DefaultAnalysisEventProcessor#endRow
,挨个调用监听器来处理数据。如果未设置ReadListener
,那么监听器集合中只有ModelBuildEventListener
。
@Override
public void endRow(AnalysisContext analysisContext) {
if (RowTypeEnum.EMPTY.equals(analysisContext.readRowHolder().getRowType())) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Empty row!");
}
if (analysisContext.readWorkbookHolder().getIgnoreEmptyRow()) {
return;
}
}
dealData(analysisContext);
}
private void dealData(AnalysisContext analysisContext) {
ReadRowHolder readRowHolder = analysisContext.readRowHolder();
Map<Integer, ReadCellData<?>> cellDataMap = (Map)readRowHolder.getCellMap();
readRowHolder.setCurrentRowAnalysisResult(cellDataMap);
int rowIndex = readRowHolder.getRowIndex();
int currentHeadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber();
boolean isData = rowIndex >= currentHeadRowNumber;
// Last head column
if (!isData && currentHeadRowNumber == rowIndex + 1) {
buildHead(analysisContext, cellDataMap);
}
// Now is data
for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) {
try {
if (isData) {
readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext);
} else {
readListener.invokeHead(cellDataMap, analysisContext);
}
} catch (Exception e) {
onException(analysisContext, e);
break;
}
if (!readListener.hasNext(analysisContext)) {
throw new ExcelAnalysisStopException();
}
}
}
ModelBuildEventListener#invoke
,ModelBuildEventListener
模型构建事件监听器用来构建模型。buildUserModel
主要是获取传入的类信息,进行实例化。将实体类转化为Map,BeanMap dataMap = BeanMapUtils.create(resultModel);
。
@Override
public void invoke(Map<Integer, ReadCellData<?>> cellDataMap, AnalysisContext context) {
ReadSheetHolder readSheetHolder = context.readSheetHolder();
if (HeadKindEnum.CLASS.equals(readSheetHolder.excelReadHeadProperty().getHeadKind())) {
context.readRowHolder()
.setCurrentRowAnalysisResult(buildUserModel(cellDataMap, readSheetHolder, context));
return;
}
context.readRowHolder().setCurrentRowAnalysisResult(buildStringList(cellDataMap, readSheetHolder, context));
}
private Object buildUserModel(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,
AnalysisContext context) {
ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty();
Object resultModel;
try {
resultModel = excelReadHeadProperty.getHeadClazz().newInstance();
} catch (Exception e) {
throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0,
new ReadCellData<>(CellDataTypeEnum.EMPTY), null,
"Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e);
}
Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();
BeanMap dataMap = BeanMapUtils.create(resultModel);
for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
Integer index = entry.getKey();
Head head = entry.getValue();
String fieldName = head.getFieldName();
if (!cellDataMap.containsKey(index)) {
continue;
}
ReadCellData<?> cellData = cellDataMap.get(index);
Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(),
ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(),
fieldName), readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), index);
if (value != null) {
dataMap.put(fieldName, value);
}
}
return resultModel;
}
ConverterUtils#doConvertToJavaObject
,获取Converter
,根据属性上的转换器进行转换操作。
private static Object doConvertToJavaObject(ReadCellData<?> cellData, Class<?> clazz,
ExcelContentProperty contentProperty, Map<ConverterKey, Converter<?>> converterMap, AnalysisContext context,
Integer rowIndex, Integer columnIndex) {
Converter<?> converter = null;
if (contentProperty != null) {
converter = contentProperty.getConverter();
}
boolean canNotConverterEmpty = cellData.getType() == CellDataTypeEnum.EMPTY
&& !(converter instanceof NullableObjectConverter);
if (canNotConverterEmpty) {
return null;
}
if (converter == null) {
converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType()));
}
if (converter == null) {
throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty,
"Converter not found, convert " + cellData.getType() + " to " + clazz.getName());
}
try {
return converter.convertToJavaData(new ReadConverterContext<>(cellData, contentProperty, context));
} catch (Exception e) {
throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty,
"Convert data " + cellData + " to " + clazz + " error ", e);
}
}
BeanMap
的使用,通过设置Map中的key,修改实体类的数据。
public static void main(String[] args) {
OrderExcel orderExcel = new OrderExcel();
BeanMap beanMap = BeanMapUtils.create(orderExcel);
beanMap.put("orderNo", "111");
System.out.println(beanMap);
System.out.println(orderExcel);
}
数据导出
demo
@PostMapping("/t4")
public void t4(HttpServletResponse response) throws Exception {
OrderExcel orderExcel = new OrderExcel();
List<OrderExcel> orderExcels = Arrays.asList(orderExcel);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
//"测试":就是我们要生成文档的名称,可以改为自己的
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), OrderExcel.class).sheet("模板").doWrite(orderExcels);
}
源码解读
ExcelWriterSheetBuilder#doWrite(java.util.Collection<?>)
,执行写入操作
public void doWrite(Collection<?> data) {
if (excelWriter == null) {
throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method");
}
excelWriter.write(data, build());
excelWriter.finish();
}
ExcelWriter#write(Collection<?>, WriteSheet)
,调用内置的ExcelBuilder
public ExcelWriter write(Collection<?> data, WriteSheet writeSheet) {
return write(data, writeSheet, null);
}
public ExcelWriter write(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable) {
excelBuilder.addContent(data, writeSheet, writeTable);
return this;
}
ExcelBuilderImpl#addContent()
,创建ExcelWriteAddExecutor
进行添加操作。
@Override
public void addContent(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable) {
try {
context.currentSheet(writeSheet, WriteTypeEnum.ADD);
context.currentTable(writeTable);
if (excelWriteAddExecutor == null) {
excelWriteAddExecutor = new ExcelWriteAddExecutor(context);
}
excelWriteAddExecutor.add(data);
} catch (RuntimeException e) {
finishOnException();
throw e;
} catch (Throwable e) {
finishOnException();
throw new ExcelGenerateException(e);
}
}
ExcelWriteAddExecutor#add
,添加数据到WorkBook中。
public void add(Collection<?> data) {
if (CollectionUtils.isEmpty(data)) {
data = new ArrayList<>();
}
WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder();
int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();
if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) {
newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex();
}
// BeanMap is out of order, so use sortedAllFieldMap
Map<Integer, Field> sortedAllFieldMap = new TreeMap<>();
int relativeRowIndex = 0;
for (Object oneRowData : data) {
int lastRowIndex = relativeRowIndex + newRowIndex;
addOneRowOfDataToExcel(oneRowData, lastRowIndex, relativeRowIndex, sortedAllFieldMap);
relativeRowIndex++;
}
}
private void addOneRowOfDataToExcel(Object oneRowData, int rowIndex, int relativeRowIndex,
Map<Integer, Field> sortedAllFieldMap) {
if (oneRowData == null) {
return;
}
RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(writeContext,
rowIndex, relativeRowIndex, Boolean.FALSE);
WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext);
Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), rowIndex);
rowWriteHandlerContext.setRow(row);
WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext);
if (oneRowData instanceof Collection<?>) {
addBasicTypeToExcel(new CollectionRowData((Collection<?>)oneRowData), row, rowIndex, relativeRowIndex);
} else if (oneRowData instanceof Map) {
addBasicTypeToExcel(new MapRowData((Map<Integer, ?>)oneRowData), row, rowIndex, relativeRowIndex);
} else {
addJavaObjectToExcel(oneRowData, row, rowIndex, relativeRowIndex, sortedAllFieldMap);
}
WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext);
}
ExcelWriteAddExecutor#addJavaObjectToExcel
,添加java实体类到Excel中国。
private void addJavaObjectToExcel(Object oneRowData, Row row, int rowIndex, int relativeRowIndex,
Map<Integer, Field> sortedAllFieldMap) {
WriteHolder currentWriteHolder = writeContext.currentWriteHolder();
BeanMap beanMap = BeanMapUtils.create(oneRowData);
// Bean the contains of the Map Key method with poor performance,So to create a keySet here
Set<String> beanKeySet = new HashSet<>(beanMap.keySet());
Set<String> beanMapHandledSet = new HashSet<>();
int maxCellIndex = -1;
// If it's a class it needs to be cast by type
if (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) {
Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap();
for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
int columnIndex = entry.getKey();
Head head = entry.getValue();
String name = head.getFieldName();
if (!beanKeySet.contains(name)) {
continue;
}
ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap,
currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), name);
CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(
writeContext, row, rowIndex, head, columnIndex, relativeRowIndex, Boolean.FALSE,
excelContentProperty);
WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);
Cell cell = WorkBookUtil.createCell(row, columnIndex);
cellWriteHandlerContext.setCell(cell);
WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);
cellWriteHandlerContext.setOriginalValue(beanMap.get(name));
cellWriteHandlerContext.setOriginalFieldClass(head.getField().getType());
converterAndSet(cellWriteHandlerContext);
WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);
beanMapHandledSet.add(name);
maxCellIndex = Math.max(maxCellIndex, columnIndex);
}
}
// Finish
if (beanMapHandledSet.size() == beanMap.size()) {
return;
}
maxCellIndex++;
Map<String, Field> ignoreMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getIgnoreMap();
initSortedAllFieldMapFieldList(oneRowData.getClass(), sortedAllFieldMap);
for (Map.Entry<Integer, Field> entry : sortedAllFieldMap.entrySet()) {
Field field = entry.getValue();
String fieldName = FieldUtils.resolveCglibFieldName(field);
boolean uselessData = !beanKeySet.contains(fieldName) || beanMapHandledSet.contains(fieldName)
|| ignoreMap.containsKey(fieldName);
if (uselessData) {
continue;
}
Object value = beanMap.get(fieldName);
ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap,
currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), fieldName);
CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(
writeContext, row, rowIndex, null, maxCellIndex, relativeRowIndex, Boolean.FALSE, excelContentProperty);
WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);
// fix https://github.com/alibaba/easyexcel/issues/1870
// If there is data, it is written to the next cell
Cell cell = WorkBookUtil.createCell(row, maxCellIndex);
cellWriteHandlerContext.setCell(cell);
WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);
cellWriteHandlerContext.setOriginalValue(value);
cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(beanMap, fieldName, value));
converterAndSet(cellWriteHandlerContext);
WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);
maxCellIndex++;
}
}
AbstractExcelWriteExecutor#doConvert
,获取对应的属性上的转换器,如果获取不到,获取converterMap
默认的转换器,调用转换器转换。
private WriteCellData<?> doConvert(CellWriteHandlerContext cellWriteHandlerContext) {
ExcelContentProperty excelContentProperty = cellWriteHandlerContext.getExcelContentProperty();
Converter<?> converter = null;
if (excelContentProperty != null) {
converter = excelContentProperty.getConverter();
}
if (converter == null) {
// csv is converted to string by default
if (writeContext.writeWorkbookHolder().getExcelType() == ExcelTypeEnum.CSV) {
cellWriteHandlerContext.setTargetCellDataType(CellDataTypeEnum.STRING);
}
converter = writeContext.currentWriteHolder().converterMap().get(
ConverterKeyBuild.buildKey(cellWriteHandlerContext.getOriginalFieldClass(),
cellWriteHandlerContext.getTargetCellDataType()));
}
if (cellWriteHandlerContext.getOriginalValue() == null && !(converter instanceof NullableObjectConverter)) {
return new WriteCellData<>(CellDataTypeEnum.EMPTY);
}
if (converter == null) {
throw new ExcelWriteDataConvertException(cellWriteHandlerContext,
"Can not find 'Converter' support class " + cellWriteHandlerContext.getOriginalFieldClass()
.getSimpleName() + ".");
}
WriteCellData<?> cellData;
try {
cellData = ((Converter<Object>)converter).convertToExcelData(
new WriteConverterContext<>(cellWriteHandlerContext.getOriginalValue(), excelContentProperty,
writeContext));
} catch (Exception e) {
throw new ExcelWriteDataConvertException(cellWriteHandlerContext,
"Convert data:" + cellWriteHandlerContext.getOriginalValue() + " error, at row:"
+ cellWriteHandlerContext.getRowIndex(), e);
}
if (cellData == null || cellData.getType() == null) {
throw new ExcelWriteDataConvertException(cellWriteHandlerContext,
"Convert data:" + cellWriteHandlerContext.getOriginalValue()
+ " return is null or return type is null, at row:"
+ cellWriteHandlerContext.getRowIndex());
}
return cellData;
}