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