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

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

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

DTO代码(开启了chain ):

@HeadRowHeight(30)
@ContentRowHeight(20)
@Data()
@Accessors(chain = true)
public class EasyExcelDTO {@ColumnWidth(30)@ExcelProperty("标题")private String title;@ColumnWidth(30)@ExcelProperty("内容")private String content;}

读取excel代码示例:

        List<EasyExcelDTO> res = new ArrayList<>();EasyExcel.read(file, EasyExcelDTO.class, new AnalysisEventListener<EasyExcelDTO>() {@Overridepublic void invoke(EasyExcelDTO o, AnalysisContext analysisContext) {res.add(o);}@Overridepublic 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的代码了

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

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

     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流程也是类似的)

在这里插入图片描述
我们可以看到关键的一行代码:

        Map setters = this.makePropertyMap(ReflectUtils.getBeanSetters(type));

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

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

        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方法

// 
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方法有返回值导致的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/218489.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态规划习题

动态规划的核心思想是利用子问题的解来构建整个问题的解。为此&#xff0c;我们通常使用一个表格或数组来存储子问题的解&#xff0c;以便在需要时进行查找和使用。 1.最大字段和 #include <iostream> using namespace std; #define M 200000int main() {int n, a[M], d…

LCR 120. 寻找文件副本

解题思路&#xff1a; 利用增强for循环遍历documents&#xff0c;将遇见的id加入hmap中&#xff0c;如果id在hamp中存在&#xff0c;则直接返回id class Solution {public int findRepeatDocument(int[] documents) {Set<Integer> hmapnew HashSet<>();for(int d…

Python+Selenium UI自动化测试环境搭建及使用

一、什么是Selenium &#xff1f; Selenium 是一个浏览器自动化测试框架&#xff0c;它主要用于web应用程序的自动化测试&#xff0c;其主要特点如下&#xff1a;开源、免费&#xff1b;多平台、浏览器、多语言支持&#xff1b;对web页面有良好的支持&#xff1b;API简单灵活易…

【C++11特性篇】盘点C++11中三种简化声明的方式【auto】【decltype】【nullptr】(3)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.auto&#xff06;范围for二.decltyp…

Java医院3D人体智能导诊系统源码 Uniapp+springboot 微信小程序

“智能导诊”以人工智能手段为依托&#xff0c;为人们提供智能分诊、问病信息等服务&#xff0c;在一定程度上满足了人们自我健康管理、精准挂号等需求。智能导诊可根据描述的部位和病症&#xff0c;给出适合病症的科室参考。 智能导诊页面会显示男性或女性的身体结构图&#x…

Linux基本操作指令

哈喽小伙伴们&#xff0c;从这篇文章开始&#xff0c;在学习数据结构的同时&#xff0c;我们开启一个新的篇章——Linux操作系统的学习&#xff0c;这将会是又一个新的开始&#xff0c;希望小伙伴们能够认真细心&#xff0c;不要掉队哦。 目录 一.什么是Linux 二.为什么要学习…

LeetCode 300最长递增子序列 674最长连续递增序列 718最长重复子数组 | 代码随想录25期训练营day52

动态规划算法10 LeetCode 300 最长递增子序列 2023.12.15 题目链接代码随想录讲解[链接] int lengthOfLIS(vector<int>& nums) {//创建变量result存储最终答案,设默认值为1int result 1;//1确定dp数组&#xff0c;dp[i]表示以nums[i]为结尾的子数组的最长长度ve…

JNA实现JAVA调用C/C++动态库

1.JNA JNA全称Java Native Access&#xff0c;是一个建立在经典的JNI技术之上的Java开源框架&#xff08;https://github.com/twall/jna&#xff09;。JNA提供一组Java工具类用于在运行期动态访问系统本地库&#xff08;native library&#xff1a;如Window的dll&#xff09;而…

CSS学习笔记整理

CSS 即 层叠样式表/CSS样式表/级联样式表&#xff0c;也是标记语言&#xff0c; 用于设置HTML页面中的文本内容&#xff08;字体、大小、对齐方式等&#xff09;、图片的外形&#xff08;宽高、边框样式、边距&#xff09;以及版面的布局和外观显示样式 目录 准备工作 Chrome调…

时间序列预测 — CNN-LSTM实现多变量多步光伏预测(Tensorflow)

目录 1 数据处理 1.1 导入库文件 1.2 导入数据集 1.3 缺失值分析 2 构造训练数据 ​3 模型训练 3.1 CNN-LSTM网络 3.2 模型训练 4 模型预测 专栏链接&#xff1a;https://blog.csdn.net/qq_41921826/category_12495091.html 1 数据处理 1.1 导入库文件 import scip…

『番外篇二』Swift “黑魔法”之动态获取类实例隐藏属性的值

概览 在 Swift 代码的调试中,我们时常惊叹调试器的无所不能:对于大部分“黑盒”类实例的内容,调试器也都能探查的一清二楚。 想要自己在运行时也能轻松找到 Thread 实例“私有”属性的值吗(比如 seqNum)? 在本篇博文中您将学到如下内容: 概览1. 借我,借我,一双慧眼吧…

深入理解LightGBM

1. LightGBM简介 GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型&#xff0c;其主要思想是利用弱分类器&#xff08;决策树&#xff09;迭代训练以得到最优模型&#xff0c;该模型具有训练效果好、不易过拟合等优点。GBDT不仅在工业界应用广泛&#…

IT 人员与加密程序:如何战胜病毒

&#x1f510; 加密程序是攻击者在成功攻击组织时使用最多的恶意软件类型。它们通常会发送到一个庞大的电子邮件地址数据库&#xff0c;看起来像 Word 或 Excel 文档或 PDF 文件。 想象一下&#xff0c;你是会计部门的一名员工。这种格式的文件在电子文档管理系统中被广泛使用…

如何查看Linux中glibc的Version

用ldd --version ldd --version 运行libc.so 你没有看错&#xff0c;libc.so是一个可执行程序。 但前提是你要找到它。因为它并不在PATH所包含的目录下。 ppdell:~$ ldd which cat | grep libclibc.so.6 > /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0e6fb34000)ppdell:~…

dcoker-compose一键部署EFAK —— 筑梦之路

简介 EFAK&#xff08;Eagle For Apache Kafka&#xff0c;以前称为 Kafka Eagle&#xff09;是一款由国内公司开源的Kafka集群监控系统&#xff0c;可以用来监视kafka集群的broker状态、Topic信息、IO、内存、consumer线程、偏移量等信息&#xff0c;并进行可视化图表展示。独…

跟随鼠标动态显示线上点的值(基于Qt的开源绘图控件QCustomPlot进行二次开发)

本文为转载 原文链接&#xff1a; 采用Qt快速绘制多条曲线&#xff08;折线&#xff09;&#xff0c;跟随鼠标动态显示线上点的值&#xff08;基于Qt的开源绘图控件QCustomPlot进行二次开发&#xff09; 内容如下 QCustomPlot是一个开源的基于Qt的第三方绘图库&#xff0c;能…

干货 | 一文搞定 pytest 自动化测试框架(一)

简介 pytest 是一个成熟的全功能 Python 测试工具&#xff0c;可以帮助您编写更好的程序。它与 Python 自带的 Unittest 测试框架类似&#xff0c;但 pytest 使用起来更简洁和高效&#xff0c;并且兼容 unittest 框架。pytest 有以下实用特性&#xff1a; pytest 能够支持简单…

Spring容器中scope为prototype类型Bean的回收机制

文章目录 一、背景二、AutowireCapableBeanFactory 方法 autowireBean 分析三、Spring 容器中 scope 为 prototype 类型 Bean 的回收机制四、总结 一、背景 最近做 DDD 实践时&#xff0c;遇到业务对象需要交给 Spring 管理才能做一些职责内事情。假设账号注册邮箱应用层代码流…

DDD落地:爱奇艺打赏服务,如何DDD架构?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#x…

【Kubernetes】存储类StorageClass

存储类StorageClass 一、StorageClass介绍二、安装nfs provisioner&#xff0c;用于配合存储类动态生成pv2.1、创建运行nfs-provisioner需要的sa账号2.2、对sa授权2.3、安装nfs-provisioner程序 三、创建storageclass&#xff0c;动态供给pv四、创建pvc&#xff0c;通过storage…