问题
使用cglib提供的net.sf.cglib.beans.BeanCopier
进行对象拷贝时,抛出如下异常:
Exception in thread "main" java.lang.NullPointerException
at net.sf.cglib.core.ReflectUtils.getMethodInfo(ReflectUtils.java:421)
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 com.jd.paytrade.CglibTest.main(CglibTest.java:12)
结论
BeanCopier类在cglib的3.2.0版本前(3.2.0已修复)有BUG,当target类含有没有定义标准set方法的属性时,会抛出上述异常。bug修复commit。在3.2.0版本后,相同情况下不会抛出异常,只不过未定义标准set方法的属性无法被成功赋值。
解决
方案一:升级项目中引入的cglib版本;
方案二:考虑到老项目升级公共组件影响范围过大,老老实实使用set、get进行对象属性拷贝;
背景
引用其他部门的jar包,对其中的类使用BeanCopier赋值,抛出上述异常。发现外部包中的Bean定义都采用了建造者模式,set方法定义类似:
public TargetBean setId(Long id) {
this.id = id;
return this;
}
而Cglib提取set方法所使用的Java自省API获取set方法的代码如下(java.beans.Introspector#getTargetPropertyInfo):
static final String SET_PREFIX = "set";
……
} else if (argCount == 1) { // 参数个数为1个
if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
} else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) { // 返回值为void类型 && set开头
// Simple setter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
由上述代码可知被识别为set方法需要满足:
- 参数个数为1个
- 返回值为void类型
- set开头
显然用建造者模式定义的Bean不符合第二条,纵使升级了Cglib版本不报错了,值也拷贝不上。而BeanUtils的拷贝性能又不行,只好改为了一个一个属性get、set。
思考
使用BeanCopier或者BeanUtils进行对象拷贝,代码是比较简洁,但一定是好的吗?
个人认为不然,在日常开发中,如果要对某些字段的赋值或使用方式进行改动,首先会想到去看看这个属性在哪里被用到了,查看方法自然是去追踪该属性的set方法与get方法。如果所有的赋值都使用BeanCopier此类工具,则没有对set、get的显示调用,排查赋值路径会很困难。
个人日常开发中只有在明确的参数透传环节并且source和target相似度在百分之九十以上才会使用此类工具,其余情况如不同层级间Bean的赋值,或者取source中部分字段给target,均会使用get、set方法进行赋值,便于后续系统的维护。并且,get、set效率是最高的(狗头)。