使用BeanCopier抛出NullPointerException溯源

问题

使用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. 参数个数为1个
  2. 返回值为void类型
  3. set开头

显然用建造者模式定义的Bean不符合第二条,纵使升级了Cglib版本不报错了,值也拷贝不上。而BeanUtils的拷贝性能又不行,只好改为了一个一个属性get、set。

思考

使用BeanCopier或者BeanUtils进行对象拷贝,代码是比较简洁,但一定是好的吗?

个人认为不然,在日常开发中,如果要对某些字段的赋值或使用方式进行改动,首先会想到去看看这个属性在哪里被用到了,查看方法自然是去追踪该属性的set方法与get方法。如果所有的赋值都使用BeanCopier此类工具,则没有对set、get的显示调用,排查赋值路径会很困难。

个人日常开发中只有在明确的参数透传环节并且source和target相似度在百分之九十以上才会使用此类工具,其余情况如不同层级间Bean的赋值,或者取source中部分字段给target,均会使用get、set方法进行赋值,便于后续系统的维护。并且,get、set效率是最高的(狗头)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孔天逸

没有钱用,只能写写博客这样子~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值