就因為加了Lombok的@Accessors(chain = true),bean拷貝工具類不幹活了

  • 2019 年 11 月 7 日
  • 筆記

前言

這次新建了一個工程,因為 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

Tags: