就因为加了Lombok的@Accessors(chain = true),bean拷贝工具类不干活了

前言#

这次新建了一个工程,因为 Lombok 用得很习惯,但以前的话,一般只用了@Data@AllArgsConstructor@EqualsAndHashCode等常规注解;那这个Accessors(chain = true)注解是干嘛的呢?

用了这个注解后,生成的set方法是这样的:

Copy
#加了Accessors(chain = true) public Devolution setCenterId(Long centerId) { this.centerId = centerId; return this; }

注意,正常情况下,方法应该是下面这样的:

Copy
#没加Accessors(chain = true) public void setCenterId(Long centerId) { this.centerId = centerId; }

为什么要用这个方法?主要是方便级联操作。基于这个考虑就加了。

加了后,出现了什么问题?

我们之前有个bean拷贝的工具类,用于在 po 和 vo 间拷贝属性。

Copy
import org.springframework.cglib.beans.BeanCopier; public static void copyProperties(Object source,Object target){ BeanCopier copier = getBeanCopier(source.getClass(), target.getClass()); copier.copy(source, target, null); }

结果,同事反映说,当target的类型,加了 Accessors(chain = true)时, 这个工具类不能用了!

跟踪问题#

我本来以为改改spring源码就可以了,结果发现org.springframework.cglib.beans.BeanCopier 源码打不开,换了个spring 4的版本,也不行。看到包里面,是待了cglib的,于是本地找了个cglib的包,发现是带source的,于是解压后导入工程,嗯,还不错,可以用!

工程代码在:

https://gitee.com/ckl111/cglib-lombok-test

我这里先说问题原因:

我找到了一个测试用例,大概如下:

Copy
public void testSimple() { BeanCopier copier = BeanCopier.create(MA.class, MA.class, false); MA bean1 = new MA(); bean1.setIntP(42); MA bean2 = new MA(); copier.copy(bean1, bean2, null); assertTrue(bean2.getIntP() == 42); }

然后自己改造了一下,加了个类:

Copy
@Data @Accessors(chain = true) class MaWithLombok { private Long id; private String name; private String privateName; private int intP; private long longP; private boolean booleanP; private char charP; private byte byteP; private short shortP; private float floatP; private double doubleP; private String stringP; public String publicField; } 这里是测试用例: public void testSimpleLombok() { BeanCopier copier = BeanCopier.create(MA.class, MaWithLombok.class, false); MA bean1 = new MA(); bean1.setIntP(42); MaWithLombok bean2 = new MaWithLombok(); copier.copy(bean1, bean2, null); assertTrue(bean2.getIntP() == 42); }

接下来,就是调试了,在不打断点直接run时,会抛下面异常:

Copy
java.lang.NullPointerException at net.sf.cglib.core.ReflectUtils.getMethodInfo(ReflectUtils.java:424) at net.sf.cglib.beans.BeanCopier$Generator.generateClass(BeanCopier.java:133) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) at net.sf.cglib.beans.BeanCopier$Generator.create(BeanCopier.java:90) at net.sf.cglib.beans.BeanCopier.create(BeanCopier.java:50) at net.sf.cglib.beans.TestBeanCopier.testSimpleLombok(TestBeanCopier.java:38)

打断点时,发现:

参数member为null,ok,把堆栈退一层(鼠标点到上一层frame)

然后寻找setter的来源:

PropertyDescriptor[] setters = ReflectUtils.getBeanGetters(target);

单步调试,会找到这个地方:

Copy
这里是进到了jdk的类,这里 java.beans.Introspector#getBeanInfo() private BeanInfo getBeanInfo() throws IntrospectionException { // the evaluation order here is import, as we evaluate the // event sets and locate PropertyChangeListeners before we // look for properties. BeanDescriptor bd = getTargetBeanDescriptor(); MethodDescriptor mds[] = getTargetMethodInfo(); EventSetDescriptor esds[] = getTargetEventInfo(); PropertyDescriptor pds[] = getTargetPropertyInfo();//在这里,获取目标类的属性描述符列表 int defaultEvent = getTargetDefaultEventIndex(); int defaultProperty = getTargetDefaultPropertyIndex(); return new GenericBeanInfo(bd, esds, defaultEvent, pds, defaultProperty, mds, explicitBeanInfo); }

我们进入该方法,下图就能告诉你为什么(java/beans/Introspector.java:520):

原因总结#

好了,经过上面的问题,大家能发现,因为我们注解的原因,导致setXXX方法的返回值不为void,所以使用

java.beans.Introspector#getTargetPropertyInfo来获取 PropertyDescriptor的时候,出现了问题。

问题解决#

问题发现了,要怎么解决呢,也简单,我google了一下,哈哈哈。

参考:https://github.com/cglib/cglib/issues/108

使用下面这个工具方法即可:

org.springframework.beans.BeanUtils.copyProperties(source, target);

我的测试工程在,如果大家需要调试 cglib源码,也可以看看,里面有很多功能的test用例:

https://gitee.com/ckl111/cglib-lombok-test




posted @   三国梦回  阅读(30933)  评论(4编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
历史上的今天:
2018-11-07 如何区分一个系统是redhat centos ubuntu fedora debian中的哪一种
点击右上角即可分享
微信分享提示
CONTENTS