• 就因为加了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

  • 相关阅读:
    Android ConstraintLayout详解
    Android开发屏幕适配解决方
    高并发场景下的一种JVM GC优化配置【CMS】
    Runnable 和 Callable的区别
    理解对象实例化顺序
    mysql 优化原理【转】
    使用@Scheduled注解编写spring定时任务
    Spring加载resource时classpath*:与classpath:的区别
    Java 8 中的 Streams API 详解
    java 8 函数式接口
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11812528.html
Copyright © 2020-2023  润新知