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>() {@Overridepublic void invoke(OrderExcel orderExcel, AnalysisContext analysisContext) {System.out.println(orderExcel);}@Overridepublic 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
。
@Overridepublic 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 columnif (!isData && currentHeadRowNumber == rowIndex + 1) {buildHead(analysisContext, cellDataMap);}// Now is datafor (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);
。
@Overridepublic 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
进行添加操作。
@Overridepublic 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 sortedAllFieldMapMap<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 hereSet<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 typeif (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);}}// Finishif (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 cellCell 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 defaultif (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;}