EasyExcel导入时invoke方法中data对象取不到值问题

在后台项目中,经常需要使用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;
    }

}

觉得内容还不错?打赏个钢镚鼓励鼓励!!👍