【源码解析】EasyExcel导入导出源码解析

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#invokeModelBuildEventListener模型构建事件监听器用来构建模型。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;
    }

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值