就因为加了Lombok的@Accessors(chain = true),bean拷贝工具类不干活了
前言
这次新建了一个工程,因为 Lombok 用得很习惯,但以前的话,一般只用了@Data,@AllArgsConstructor,@EqualsAndHashCode等常规注解;那这个Accessors(chain = true)注解是干嘛的呢?
用了这个注解后,生成的set方法是这样的:
#加了Accessors(chain = true)
public Devolution setCenterId(Long centerId) {
        this.centerId = centerId;
        return this;
}
注意,正常情况下,方法应该是下面这样的:
#没加Accessors(chain = true)
public void setCenterId(Long centerId) {
        this.centerId = centerId;
}
为什么要用这个方法?主要是方便级联操作。基于这个考虑就加了。
加了后,出现了什么问题?
我们之前有个bean拷贝的工具类,用于在 po 和 vo 间拷贝属性。
	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
我这里先说问题原因:
我找到了一个测试用例,大概如下:
    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);
    }
然后自己改造了一下,加了个类:
@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时,会抛下面异常:
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);
单步调试,会找到这个地方:
这里是进到了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
就因为加了Lombok的@Accessors(chain = true),bean拷贝工具类不干活了的更多相关文章
- Mybatis  实体类使用@Accessors(chain = true)注解时,对应的mapper  xml 报错
		
去掉这个注解就行了 应该是 mybatis 会调用实体类的 getter setter 方法, 返回值可能会有所影响
 - lombok的@Accessors注解
		
@AllArgsConstructor @Data @NoArgsConstructor @Accessors(chain = true) @EqualsAndHashCode public clas ...
 - lombok的@Accessors注解3个属性说明
		
https://www.cnblogs.com/kelelipeng/p/11326936.html https://www.cnblogs.com/kelelipeng/p/11326621.htm ...
 - lombok使用指南,代码极简工具
		
我们的项目中会用到各种bean,比如vo,bo,dto等等,bean上的属性我们一般写get(),set()方法,整个java文件看起来很臃肿. 一.简介 我们今天介绍的lombok只用使用注解就可以 ...
 - android html 图片处理类--加载富文本工具类
		
在android开发中,一些资讯类页面,里面有html标签和图片,html 标签一般通过Html.fromHtml方法,即可以解决,但是如果html 有图片标签,那么,Html.fromHtml 好像 ...
 - velocity merge作为工具类从web上下文和jar加载模板的两种常见情形
		
很多时候,处于各种便利性或折衷或者通用性亦或是限制的原因,会借助于模板生成结果,在此介绍两种使用velocity merge的情形,第一种是和spring mvc一样,将模板放在velocityCon ...
 - Json工具类,实现了反射将整个Object转换为Json对象的功能,支持Hibernate的延迟加
		
package com.aherp.framework.util; import java.lang.reflect.Array;import java.lang.reflect.Method;imp ...
 - Android加载网络图片的工具类
		
ImageView加载网络的图片 HttpUtil.java package com.eiice.httpuimagetils; import java.io.ByteArrayOutputStrea ...
 - Android自定义圆形图片工具类(CTRL+C加CTRL+V直接使用)
		
先贴一下工具类的代码!可直接复制粘贴 public class RoundImageView extends ImageView { private Paint mPaint; //画笔 privat ...
 
随机推荐
- SUSE Ceph 快速部署 - Storage6
			
学习 SUSE Storage 系列文章 (1)SUSE Storage6 实验环境搭建详细步骤 - Win10 + VMware WorkStation (2)SUSE Linux Enterpri ...
 - Kubernetes 系列(三):Kubernetes使用Traefik Ingress暴露服务
			
一.Kubernetes 服务暴露介绍 从 kubernetes 1.2 版本开始,kubernetes提供了 Ingress 对象来实现对外暴露服务:到目前为止 kubernetes 总共有三种暴露 ...
 - Spring Boot 2.X(二):集成 MyBatis 数据层开发
			
MyBatis 简介 概述 MyBatis 是一款优秀的持久层框架,支持定制化 SQL.存储过程以及高级映射.它采用面向对象编程的方式对数据库进行 CRUD 的操作,使程序中对关系数据库的操作更方便简 ...
 - Java读源码之Thread
			
前言 JDK版本:1.8 阅读了Object的源码,wait和notify方法与线程联系紧密,而且多线程已经是必备知识,那保持习惯,就从多线程的源头Thread类开始读起吧.由于该类比较长,只读重要部 ...
 - 解决MVC中Model上的特性在EF框架刷新时清空的问题
			
MVC中关于前端数据的效验一般都是通过在Model中相关的类上打上特性来实现. 但是在我们数据库发生改变,EF框架需要刷新时会把我们在Model上的特性全部清除,这样的话,我们前端的验证就会失效. 因 ...
 - JDK-基于Windows环境搭建
			
JDK安装: 毋庸置疑你要跑java程序,肯定少不了JDK,如jemter还有还有~ 下载jdk地址1:https://pan.baidu.com/s/1FIvGNvZSy0EpCBxHCz07nA ...
 - mac下安装rabbitmq
			
使用homebrew安装rabbitmq,命令如下: brew install rabbitmq 安装的位置如下/usr/local/Cellar/rabbitmq/3.7.18 进入到sbin目录下 ...
 - vue路由跳转的方式
			
vue路由跳转有四种方式 1. router-link 2. this.$router.push() (函数里面调用) 3. this.$router.replace() (用法同push) 4. t ...
 - 【包教包会】Chrome拓展开发实践
			
首发于微信公众号<前端成长记>,写于 2019.10.18 导读 有句老话说的好,好记性不如烂笔头.人生中,总有那么些东西你愿去执笔写下. 本文旨在把整个开发的过程和遇到的问题及解决方案记 ...
 - Vue-CLI 项目在pycharm中配置
			
Vue-CLI Vue-CLI 项目在pycharm中配置 第一步 pycharm索引到vue项目的根目录,打开 第二步 安装vue.js插件来高亮 .vue 文件代码(见插图) 第三步 第四步 配置 ...