EasyExcel实战与笔记
概述,入门,导入导出;问题:NoClassDefFoundError,DefaultTempFileCreationStrategy;ExcelGenerateException: Calling the 'fill' method must use a template;NullPointerException
概述
Excel导入导出是业务开发中非常常见的需求。本文记录一下如何快速入门使用EasyExcel,深度实战,以及遇到的问题。
入门
使用EasyExcel导入如下依赖即可:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
使用EasyExcel主要是两类场景:
- 上传Excel文件并解析Sheet里数据,数据校验,脏数据清除,数据落库等;
- 从数据库里查询到数据后,下载导出为Excel;
一般而言,实现上面两个需求场景时,都会提供一个工具类
/**
* excel 导出
*
* @param fileName fileName
* @param fileNameTemplate 需要提供模板
* @param data list<PO>
* @throws IOException 调用方处理
*/
public static ResponseEntity<Resource> excelExport(String fileName, String fileNameTemplate, Object data) throws IOException {
/*
* 填充文件 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 {} 代表普通变量 {.} 代表是list的变量
*/
InputStream inputStream = getResourcesFileInputStream(fileNameTemplate);
fileName = fileName + ".xlsx";
ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(inputStream).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
writeSheet.setSheetName(fileName);
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.VERTICAL).build();
excelWriter.fill(data, fillConfig, writeSheet);
// 关闭流
excelWriter.finish();
File dbfFile = new File(fileName);
if (!dbfFile.exists()) {
dbfFile.createNewFile();
}
HttpHeaders headers = new HttpHeaders();
headers.add("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
InputStreamResource resource = new InputStreamResource(new FileInputStream(dbfFile));
return ResponseEntity.ok().headers(headers).contentLength(dbfFile.length()).contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8")).body(resource);
}
private static InputStream getResourcesFileInputStream(String fileName) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName);
}
上传
导出
Controller层接口使用上面的excelExport导出方法:
@ApiOperation(value = "数据统计服务-数据概览-性别", notes = "数据概览-性别")
@PostMapping("/overview/sex/excel")
public ResponseEntity<Resource> overviewSex(HttpServletResponse response, @RequestBody CommonQueryParam param) throws IOException {
// 仅作为示例,一般不会在controller层写业务biz逻辑
List<SexExcel> sexExcelList = getList(param);
return ExcelUtils.excelExport("性别数据", "sexTemplate.xlsx", sexExcelList);
}
然后提供一个sexTemplate.xlsx模板文件,放在classpath路径下面,一般都是src/main/resources
路径下面:
对应于Excel模板文件的POJO实体类定义如下:
@Data
public class SexExcel {
private String date;
private String source;
private String sex;
private Long number;
}
需要注意的就是controller层接口请求参数里必须要有HttpServletResponse response
,返回类型得是ResponseEntity<Resource>
基本上入门使用就是上面这些内容。
进阶
动态添加自增序号列
想要实现的效果:
实现方案,增加一个自定义的RowWriteHandler:
private static class CustomRowWriteHandler implements RowWriteHandler {
private static final String FIRST_CELL_NAME = "序号";
/**
* 列号
*/
private int count = 1;
@Override
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer integer, Integer integer1, Boolean aBoolean) {
}
@Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer integer, Boolean aBoolean) {
Cell cell = row.createCell(0);
if (row.getRowNum() == 0) {
cell.setCellValue(FIRST_CELL_NAME);
}
cell.setCellValue(++count);
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer integer, Boolean aBoolean) {
}
}
注册此RowWriteHandler:
ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(inputStream).registerWriteHandler(new CustomRowWriteHandler()).build();
问题
记录几个在使用EasyExcel时遇到的问题。
NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
背景:之前已离职同事写的屎山代码,使用原生poi导出Excel,问题太多,包括但不限于接口超时,CPU飙高等。
还有个流关闭异常的报错:failed UT010029: Stream is closed
,因为打印日志时使用e.getMessage()
,没有打印出报错堆栈。
网络上关于原生POI的问题不要太多,包括office 2007和2010适配,占用内存高,占用CPU,代码冗长等。
于是,有了用EasyExcel替换原生POI的想法。导入EasyExcel,Debug模式启动应用失败,详细的报错信息如下:
Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1054)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:645)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
很常见的报错,参考Java学习之NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError。NoClassDefFoundError一般是多个版本的三方jar包并存时依赖冲突导致。
检查一下pom.xml
文件,之前使用原生POI导出引入版本号:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.11</version>
</dependency>
改造引入的EasyExcel-2.2.11,依赖的版本为:
删除poi-3.11
版本解决问题。
ExcelGenerateException: Calling the ‘fill’ method must use a template
报错日志为:
at com.alibaba.excel.write.ExcelBuilderImpl.fill(ExcelBuilderImpl.java:72)
at com.alibaba.excel.ExcelWriter.fill(ExcelWriter.java:185)
at com.aba.common.utils.ExcelUtils.excelExport(ExcelUtils.java:47)
定义好Excel模板,也定义好POJO实体类,但是在业务编码时,会发现另一个拼写更适合一些,比如把name
换成alias
,两边不一致时,就有这个问题。
@Data
public class DaTongUser {
private Integer index;
private String name;
private String mobile;
}
对应的Excel模板文件:
NullPointerException: null
具体的报错信息:
at com.alibaba.excel.write.executor.ExcelWriteFillExecutor.doFill(ExcelWriteFillExecutor.java:191)
at com.alibaba.excel.write.executor.ExcelWriteFillExecutor.fill(ExcelWriteFillExecutor.java:118)
at com.alibaba.excel.write.ExcelBuilderImpl.fill(ExcelBuilderImpl.java:78)
at com.alibaba.excel.ExcelWriter.fill(ExcelWriter.java:185)
at com.aba.common.utils.ExcelUtils.excelExport(ExcelUtils.java:47)
使用的版本号为:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.11</version>
</dependency>
报错的源码如下:
public void fill(Object data, FillConfig fillConfig) {
if (data == null) {
data = new HashMap<String, Object>(16);
}
if (fillConfig == null) {
fillConfig = FillConfig.builder().build(true);
}
fillConfig.init();
Object realData;
if (data instanceof FillWrapper) {
FillWrapper fillWrapper = (FillWrapper) data;
currentDataPrefix = fillWrapper.getName();
realData = fillWrapper.getCollectionData();
} else {
realData = data;
currentDataPrefix = null;
}
currentUniqueDataFlag = uniqueDataFlag(writeContext.writeSheetHolder(), currentDataPrefix);
// processing data
if (realData instanceof Collection) {
// 报错根源
List<AnalysisCell> analysisCellList = readTemplateData(templateCollectionAnalysisCache);
Collection collectionData = (Collection) realData;
if (CollectionUtils.isEmpty(collectionData)) {
return;
}
Iterator iterator = collectionData.iterator();
if (WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection()) && fillConfig.getForceNewRow()) {
shiftRows(collectionData.size(), analysisCellList);
}
while (iterator.hasNext()) {
// 报错方法
doFill(analysisCellList, iterator.next(), fillConfig, getRelativeRowIndex());
}
} else {
doFill(readTemplateData(templateAnalysisCache), realData, fillConfig, null);
}
}
private List<AnalysisCell> readTemplateData(Map<String, List<AnalysisCell>> analysisCache) {
List<AnalysisCell> analysisCellList = analysisCache.get(currentUniqueDataFlag);
if (analysisCellList != null) {
return analysisCellList;
}
Sheet sheet = writeContext.writeSheetHolder().getCachedSheet();
Map<String, Set<Integer>> firstRowCache = new HashMap<String, Set<Integer>>(8);
for (int i = 0; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue;
}
for (int j = 0; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
if (cell == null) {
continue;
}
String preparedData = prepareData(cell, i, j, firstRowCache);
// Prevent empty data from not being replaced
if (preparedData != null) {
cell.setCellValue(preparedData);
}
}
}
return analysisCache.get(currentUniqueDataFlag);
}
看不明白在干嘛。时间不允许,没有好好去钻研。
GitHub issue里找不到相关信息。把依赖的升级版本号到3.0.1
,报错:
java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.http.ResponseEntity["body"]->org.springframework.core.io.InputStreamResource["inputStream"]->java.io.FileInputStream["fd"])
增加如下配置:
static {
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
报错消失,但是导出的Excel为空!??
值得注意的是:
在使用EasyExcel 2.2.11版本时,基于上面的EasyExcel
导出工具类,是没有问题的,可以导出Excel文件,且有数据。
升级到3.0.1
版本后,导出文件为空。
导出Excel文件为空
上面提到的问题。实在是搞不定,在GitHub Issue里提个导出Excel为空的Issue。
解决方法:
删除这一行:writeSheet.setSheetName(fileName);
我:一脸懵逼???
参考
更多推荐
所有评论(0)