【lombok】从easyExcel read不到值到cglib @Accessors(chain = true)隐藏的大坑

背景:

在一次使用easyExcel.read 读取excel时,发现实体类字段没有值,在反复测试后,发现去掉@Accessors(chain = true)就正常了,为了验证原因,进行了一次代码跟踪

由于调用链路特别长,只列举出部分代码, 感兴趣的同学通过断点及前后的堆栈信息可以自己追踪到中间代码。

DTO代码(开启了chain ):

java 复制代码
@HeadRowHeight(30)
@ContentRowHeight(20)
@Data()
@Accessors(chain = true)
public class EasyExcelDTO {
    @ColumnWidth(30)
    @ExcelProperty("标题")
    private String title;

    @ColumnWidth(30)
    @ExcelProperty("内容")
    private String content;

}

读取excel代码示例:

java 复制代码
        List<EasyExcelDTO> res = new ArrayList<>();

        EasyExcel.read(file, EasyExcelDTO.class, new AnalysisEventListener<EasyExcelDTO>() {

            @Override
            public void invoke(EasyExcelDTO o, AnalysisContext analysisContext) {
                res.add(o);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {

            }
        }).sheet().doRead();

        System.out.println(res);

首先我们从doRead()方法点进去:

ExcelReaderSheetBuilder类的doRead()方法

接着连续点几次read()方法 过程略

接下来可以看到如下代码:

省略一系列中间步骤 (可自行通过前后断点 看到中间堆栈链路)

接下来可以看到DefaultAnalysisEventProcessor类中 readListener监听的invoke方法

ModelBuildEventListener类的buildUserModel方法,

下图中的resultModel就是我们的实体类对象

接着就是一系列的convert操作:

ConverterUtils类doConvertToJavaObject方法

接下来的重点来了:

还是在ModelBuildEventListener类的buildUserModel方法中,最后有两行,

为什么会盯上这两行代码呢 ,因为这里返回去 字段没有值,意味着这个步骤出现了问题,也正是从这里开始 与阿里无关(阿里成功甩锅),接下来就是使用的cglib的代码了

java 复制代码
        BeanMap.create(resultModel).putAll(map);
        return resultModel;

从这里开始 我们可以不用分析easyExcel的代码了,我们的demo也可以转换为 (因为没有监听 更方便调试):

java 复制代码
     EasyExcelDTO easyExcelDTO = new EasyExcelDTO();
        Map<String, String> map = new HashMap<>();
        // DTO里面有title字段
        map.put("title","1");
        // 相当于bean拷贝 (下面这行是cglib里面的代码)
        BeanMap.create(easyExcelDTO).putAll(map);
        System.out.println(easyExcelDTO);

putAll只是个赋值,所以我们看cglib包下的BeanMap类的 create()方法:

跟踪的难点 难就难在不知道到底哪个步骤对bean进行操作 ,

接下来是AbstractClassGenerator类的create方法

再接下来是ReflectUtils类的getPropertiesHelper方法:

为什么会找到这个方法 因为它被getBeanSetters方法调用,而bean拷贝赋值 大概率就是通过set方法去设置值的,也就是说问题可能出在set方法里面

从这里开始,调用的就是java.desktop包下的代码了 通俗点说也就是jdk源码

接下来是Introspector类的processPropertyDescriptors()方法

再紧接着就是PropertyDescriptor类的构造方法了 有些代码逻辑 不管是get 还是set方法 都会执行一遍

因为我断点只打在了下图setters ,上面是get方法的步骤 其实在之前的步骤中 还需要经过cglib BeanMapEmitter类的构造方法 (set流程也是类似的)

我们可以看到关键的一行代码:

java 复制代码
        Map setters = this.makePropertyMap(ReflectUtils.getBeanSetters(type));

这边就是获取set方法map了,那如果这个map没有内容 是不是说明我们错过了什么调试步骤呢?我们需要做的应该是往上游找代码, 没必要继续往下跟源码了

我们代码跟下来,似乎信息都是在 entry里面,我们往上翻两步可以看到下列代码:

java 复制代码
        PropertyInfo info = entry.getValue();
        setName(Introspector.decapitalize(base));
        setReadMethod0(info.getReadMethod());
        setWriteMethod0(info.getWriteMethod());

于是我们再次回到这个方法:

Introspector类的processPropertyDescriptors()方法

光标这行代码中 有个.getProperties()方法 , 我们点进去看看 ,

进入到了ClassInfo类中 有个get方法

进入上图红框的get方法,来到了PropertyInfo类的get方法,至此真相大白,

针对set方法的返回值做了判断,如果不为空 writeList就不会赋值

就找不到写入(set)相关的方法

上面我们分析的是create方法, 我们接下来简单看一下put方法

java 复制代码
// 
BeanMap.create(easyExcelDTO).putAll(map)

BeanMap类的putAll方法:

最终是一个抽象方法,那么我们可以想到 这里是用了动态代理去实现,

红框中var1是bean, bean是由我们DTO对象转换来的,var2 var3分别是k v,

不难猜测这个方法里面是对bean进行赋值

我们可以通过artuas 看一下代理对象中 put方法赋值做了什么:

tips:如何寻找代理对象?

我们通过put方法 不难看出 是给DTO(bean)赋值,意味着我们的DTO对象可能被代理了

启动arthuas 输入命令:

复制代码
 dump *EasyExcelDTO*

果然发现了代理对象:

接着通过jad 命令,输入全路径类名即可:

开启了chain 我们可以看到put方法里面没有set的步骤

关闭chain之后 有set步骤

总结: 完整调用链路中 涉及到 ReflectUtils 和 BaseMap 类,比较多工具框架都可能使用到这些代码,出现问题时,通常会先尝试找各种原因 花大量时间排除其它原因导致的 比较难想到是因为set方法有返回值导致的。

相关推荐
你是理想2 小时前
wait 和notify ,notifyAll,sleep
java·开发语言·jvm
helloworld工程师3 小时前
【微服务】SpringBoot整合LangChain4j 操作AI大模型实战详解
java·eclipse·tomcat·maven
Java&Develop3 小时前
idea里面不能运行 node 命令 cmd 里面可以运行咋回事啊
java·ide·intellij-idea
q567315233 小时前
使用Java的HttpClient实现文件下载器
java·开发语言·爬虫·scrapy
你们补药再卷啦4 小时前
不用额外下载jar包,idea快速查看使用的组件源码
java·ide·intellij-idea
爱的叹息4 小时前
Spring Boot 自定义配置类(包含字符串、数字、布尔、小数、集合、映射、嵌套对象)实现步骤及示例
java·linux·spring boot
@西瓜@4 小时前
JAVAEE(多线程-线程池)
java·开发语言
returnShitBoy5 小时前
Go语言中的垃圾回收是如何工作的?
java·jvm·golang
有什么东东6 小时前
山东大学软件学院创新项目实训开发日志(9)之测试前后端连接
java
zhangpeng4555479406 小时前
用Java写一个MVCC例子
java·开发语言