前言

这次新建了一个工程,因为 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拷贝工具类不干活了的更多相关文章

  1. Mybatis 实体类使用@Accessors(chain = true)注解时,对应的mapper xml 报错

    去掉这个注解就行了 应该是 mybatis 会调用实体类的 getter  setter 方法, 返回值可能会有所影响

  2. lombok的@Accessors注解

    @AllArgsConstructor @Data @NoArgsConstructor @Accessors(chain = true) @EqualsAndHashCode public clas ...

  3. lombok的@Accessors注解3个属性说明

    https://www.cnblogs.com/kelelipeng/p/11326936.html https://www.cnblogs.com/kelelipeng/p/11326621.htm ...

  4. lombok使用指南,代码极简工具

    我们的项目中会用到各种bean,比如vo,bo,dto等等,bean上的属性我们一般写get(),set()方法,整个java文件看起来很臃肿. 一.简介 我们今天介绍的lombok只用使用注解就可以 ...

  5. android html 图片处理类--加载富文本工具类

    在android开发中,一些资讯类页面,里面有html标签和图片,html 标签一般通过Html.fromHtml方法,即可以解决,但是如果html 有图片标签,那么,Html.fromHtml 好像 ...

  6. velocity merge作为工具类从web上下文和jar加载模板的两种常见情形

    很多时候,处于各种便利性或折衷或者通用性亦或是限制的原因,会借助于模板生成结果,在此介绍两种使用velocity merge的情形,第一种是和spring mvc一样,将模板放在velocityCon ...

  7. Json工具类,实现了反射将整个Object转换为Json对象的功能,支持Hibernate的延迟加

    package com.aherp.framework.util; import java.lang.reflect.Array;import java.lang.reflect.Method;imp ...

  8. Android加载网络图片的工具类

    ImageView加载网络的图片 HttpUtil.java package com.eiice.httpuimagetils; import java.io.ByteArrayOutputStrea ...

  9. Android自定义圆形图片工具类(CTRL+C加CTRL+V直接使用)

    先贴一下工具类的代码!可直接复制粘贴 public class RoundImageView extends ImageView { private Paint mPaint; //画笔 privat ...

随机推荐

  1. jar包冲突了?如何确定是和哪个jar包冲突了?

    导读:工程编译的时候好好地,怎么一运行就报各种的NoSuch***Error,猜测可能是jar包冲突了,但是究竟是和哪个jar包冲突了呢. 关键词:jar包冲突,NoSuchFileldError,N ...

  2. vue平行组件传值

    平行组件传值 通过平行组件传值可以实现任何情境下的传值,包括(父传子,子传父) 代码示例 <!DOCTYPE html> <html lang="en"> ...

  3. 基于vue-cli、elementUI的Vue超简单入门小例子

    - 这个例子还是比较简单的,独立完成后,能大概知道vue是干嘛的,可以写个todoList的小例子. - 开始写例子之前,先对环境的部署做点简单的介绍,其实和Vue官方的差不多. #如若没有安装过vu ...

  4. [Android Studio] 2019年Android Studio配置指北

    Android Studio是我学习Android开发路上的第一块绊脚石,新建一个项目,一行代码没动,直接编译不起来,我太难了,所以本文叫指北 本文讲解在9102年如何在国内网络不通畅的情况下流畅的使 ...

  5. springmvc中将servlet api对象作为处理方法的入参使用

    在springmvc中,控制器不依赖任何servlet api对象,也可以将servlet api对象作为处理方法的入参使用,非常方便,比如需要使用HttpSession对象,那么就可以直接将Http ...

  6. springboot项目目录结构

    idea新建springboot项目 按默认下一步至完成,默认目录结构如下 pom.xml文件内容如下 <?xml version="1.0" encoding=" ...

  7. 超炫酷的 IntelliJ IDEA 插件(一)

    工善其事必先利器 打开setting文件选择Plugins选项 Ctrl + Alt + S File -> Setting 我的idea是最新版本2019.02 有的和别人界面可能不一样 主界 ...

  8. Redis 复制过程详解

    Redis 的复制功能分为同步( sync )和命令传播( command propagate )两个步骤: 同步用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态. 命令传播则用于在主服务 ...

  9. 关于padding被计算在width中问题——box-sizing相关

    目录 盒子模型 与box-sizing有什么关系 我们为什么要开历史的"倒车" bootstrap怎么解决的 控件的box-sizing 注意甄别 前一阵子遇到一个小问题,在同样的 ...

  10. Cocos Creator 通用框架设计 —— 资源管理

    如果你想使用Cocos Creator制作一些规模稍大的游戏,那么资源管理是必须解决的问题,随着游戏的进行,你可能会发现游戏的内存占用只升不降,哪怕你当前只用到了极少的资源,并且有使用cc.loade ...