欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

EasyExcel与@Accessors(chain = true)不兼容分析

程序员文章站 2022-04-15 20:05:01
EasyExcelEasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel. github地址: https://github.com/alibaba/easyexcelAccessors@Accessors 注解用来配置lombok如何产生和显示getters和setters的方法。public @interface Accessors {/** * 如果为true,则访问器将以该字段命名,并且不包含 get 或 set...

EasyExcel

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel. github地址: https://github.com/alibaba/easyexcel

Accessors

@Accessors 注解用来配置lombok如何产生和显示getters和setters的方法。

public @interface Accessors { /**
	 * 如果为true,则访问器将以该字段命名,并且不包含 get 或 set *前缀,且省略了chain,则 chain 默认为true。 
	 * *默认值:false
	 */ boolean fluent() default false; /**
	 * *如果为true,setter将返回this而不是void。 
	 * *默认:false 
	 */ boolean chain() default false; String[] prefix() default {}; } 

测试案例

pom.xml

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> </dependency> 

Student.java

@Data @Accessors(chain = true) public class Student { @ExcelProperty("编码") private Long id; @ExcelProperty("姓名") private String name; @ExcelProperty("性别") private String sex; @ExcelProperty("年龄") private Integer age; } 

StudentControllerTest.java

public class StudentControllerTest { /**
     * 导出Excel上传模板
     */ @Test public void importTemplate() { String filePath = "D:/student.xlsx"; EasyExcel.write(filePath, Student.class).sheet("student").doWrite(null); } /**
     * 导入Excel数据
     */ @Test public void importExcel() { String filePath = "D:/student.xlsx"; List<Object> list = EasyExcel.read(filePath, Student.class, null).sheet("student").doReadSync(); for (Object obj : list) { System.out.println(obj.toString()); } } } 

student.xlsx 数据
EasyExcel与@Accessors(chain = true)不兼容分析
执行 importExcel 导入Excel 结果: 可以看到, 导入数据条数能解析, 但数据没有读取成功
EasyExcel与@Accessors(chain = true)不兼容分析

原因详解

通过删减无关代码的方法, 发现当删减 @Accessors(chain = true)或修改为 @Accessors(chain = false)后, 能够正常读取到了, 定位到是该注解的影响, 为什么会影响呢? 带着这个疑问我们继续往下看

EasyExcel 解析Excel 过程

这里不就过多赘述了, 大家可以查看如下两个文章进行了解
EasyExcel对Excel文件的解析过程
easyexcel的源码简单分析

ModelBuildEventListener

private Object buildUserModel(Map<Integer, CellData> cellDataMap, ReadHolder currentReadHolder, AnalysisContext context) { ExcelReadHeadProperty excelReadHeadProperty = currentReadHolder.excelReadHeadProperty(); Object resultModel; try { resultModel = excelReadHeadProperty.getHeadClazz().newInstance(); } catch (Exception e) { throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0, new CellData(CellDataTypeEnum.EMPTY), null, "Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e); } Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap(); Map<String, Object> map = new HashMap<String, Object>(headMap.size() * 4 / 3 + 1); Map<Integer, ExcelContentProperty> contentPropertyMap = excelReadHeadProperty.getContentPropertyMap(); for (Map.Entry<Integer, Head> entry : headMap.entrySet()) { Integer index = entry.getKey(); if (!cellDataMap.containsKey(index)) { continue; } CellData cellData = cellDataMap.get(index); if (cellData.getType() == CellDataTypeEnum.EMPTY) { continue; } ExcelContentProperty excelContentProperty = contentPropertyMap.get(index); Object value = ConverterUtils.convertToJavaObject(cellData, excelContentProperty.getField(), excelContentProperty, currentReadHolder.converterMap(), currentReadHolder.globalConfiguration(), context.readRowHolder().getRowIndex(), index); if (value != null) { map.put(excelContentProperty.getField().getName(), value); } } BeanMap.create(resultModel).putAll(map); return resultModel; } 

当Excel每行数据解析后, 会调用 buildStringList 将解析到的数据由Map转存到Bean中, 关键代码如下:

BeanMap.create(resultModel).putAll(map); 

可见, EasyExcel是使用 net.sf.cglib.beans.BeanMap 工具类拷贝的, 这正是造成 使用@Accessors(chain = true)后, EasyExcel解析不到数据的原因所在.

为什么使用了@Accessors(chain = true)后, BeanMap会拷贝不成功呢?

@Accessors(chain = true) 的作用

使set方法返回的不是void, 而是当前对象, 例如:

// 不加 @Accessors(chain = true) 时 @Data public class Student { private String name; } // 相当于 public class Student { private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } } // 加了 @Accessors(chain = true) 时 @Data @Accessors(chain = true) public class Student { private String name; } // 相当于, 添加的好处: 可以支持 new Student().setName("zhang").setXXX("xxx").setXXXX("XXX")这样的操作 public class Student { private String name; public String getName() { return this.name; } public Student setName(String name) { this.name = name; return this; } } 

BeanMap 从Map拷贝到Bean的测试

参考: 使用CGlib实现Bean拷贝(BeanCopier)
可见, 需要使用BeanMap从Map拷贝到Bean, 需要Map 的Key与Bean的变量名一致, 并有对应的 set方法, 且set方法为 void, 才能拷贝成功

已经找到了原因, 如何解决呢? 到GitHub里看了看, 作者表示现在还没法解决, 小编只能暂时不用 @Accessors(chain = true), 还是提醒广大网友, 在使用MybatisPlus生成代码时,一定要注意是否带有@Accessors(chain = true)注解,如果带有,会影响EasyExcel读取数据哦
EasyExcel与@Accessors(chain = true)不兼容分析

本文地址:https://blog.csdn.net/qq_28036249/article/details/108035369

相关标签: Spring easyexcel