在后台项目中,经常需要使用excel进行数据的导入,我们可以使用阿里巴巴的EasyExcel快速实现。
在一次需求开发中发现,使用EasyExcel导入时,在invoke方法中data对象的属性值一直取不到,全是null值,非常的疑惑。经过排查,发现其实是EasyExcel和Lombok结合使用导致的问题,或者说是cglib中BeanMap和Lombok结合的问题。(解决办法直接拉到文末)
发生属性值取不到的原因是:
1、在对象上使用了@Data注解
Lombok会默认帮对象生成set方法,这里没有问题,但是Lombok为了方便链式的调用set,生成的是带返回值的set方法,而非正常的void的set方法。
2、EasyExcel中,对invoke方法中的data对象的赋值,是使用的cglib中BeanMap类实现的
赋值的代码在ModelBuildEventListener这个类中,查看buildUserModel方法的倒二行
BeanMap.create(resultModel).putAll(map);
3、cglib中的BeanMap只会对普通的返回值为void的set方法有效,而带返回值的set方法会被忽略掉。
正是由于这一点,所以导致上述问题的产生。通过查看BeanMap的源码,最后使用的是:
abstract public Object put(Object bean, Object key, Object value);
这么一个抽象方法,具体的实现看不到(应该是cglib代理实现了对应的实现,暂不知道如何进行查看)
不过经过万能的百度,排查到,相关的代码位于:
java.beans.Introspector#getTargetPropertyInfo
方法中的
if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
} else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
// Simple setter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
其中if判断了返回值为void的类型才会有效
问题原因定位到了,那么怎么解决呢?有以下几种方法:
1、升级EasyExcel版本号(未验证过,记得以前其他项目好像没这个问题,应该新版本解决了)
2、导入的对象上不使用@Data注解,手写get和set方法
3、导入的对象上继续使用@Data注解,但是需要增加一个@Accessors(chain = false),指定生成的set方法不使用链式结构,也就是返回值为void
参考资料:
https://blog.csdn.net/weixin_42882845/article/details/119517758
相关版本号:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
附一个测试代码:
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("field1", "name");
map.put("field2", "name");
Model model = new Model();
BeanMap.create(model).putAll(map);
System.out.println(model.getField1());
System.out.println(model.getField2());
}
}
class Model{
private String field1;
private String field2;
public void setField1(String field1) {
this.field1 = field1;
}
public Model setField2(String field2) {
this.field2 = field2;
return this;
}
public String getField1() {
return field1;
}
public String getField2() {
return field2;
}
}