错误案例
短信平台大量的使用到了org.apache.commons.beanutils.BeanUtils的copyProperties(Object dest, Object orig)方法。而这个方法存在性能问题,在性能优化小组的建议下改使用net.sf.cglib.beans.BeanCopier的copy(Object from, Object to, Converter converter)方法将其替换。例如:
原来代码: BeanUtils.copyProperties(cpInfoDO, cpInfo); 改写后的代码: BeanCopier beanCopier = BeanCopier.create(CPInfo.class, CPInfoDO.class, false); beanCopier.copy(cpInfo, cpInfoDO, null);
自测时发现修改短信平台内容提供商的form提交总是失败,webx.log抛出的异常显示,插入数据库时,DO对象中非空字段出现了空值。
错误分析
比较了copy属性的两个对象,CPInfo中的status属性是StatusEnum类型(该类型继承了com.alibaba.common.lang.enumeration.IntegerEnum),而CPInfoDO中的status属性是String类型的。
1.使用BeanUtils进行属性拷贝时,CPInfo中的status属性值可以拷贝到CPInfo中的status属性值中,虽然两者类型不相同,而BeanCopier却不行。
结合本例分析BeanUtils的拷贝属性是通过反射实现的,关键代码实现和分析如下:
BeanUtilsBean: 392 descriptor =getPropertyUtils().getPropertyDescriptor(target, name); // 获得属性描述,这里target为CPInfoDO的一个实例,name为status ... 400 type = descriptor.getPropertyType(); // 获得属性的类型,CPInfoDO的类型是String ... 441 Converter converter = getConvertUtils().lookup(type); // 查找String类型的转换器,StringConverter 442 if (converter != null) { 443 log.trace(" USING CONVERTER " + converter); 444 value = converter.convert(type, value); // 进行转换,value是CPInfo实例的status属性 445 } StringConverter: public Object convert(Class type, Object value) { if (value == null) { return ((String) null); } else { return (value.toString()); // 虽然StatusEnum不是String类型,但是它有toString方法,所以不同类型的属性还是可以copy成功 } }
2.BeanCopier会通过字节码生成动态代理对象,代理对象拷贝属性时会检查类型,类型不同认为是不同的属性,就不进行赋值拷贝了。
本例中CPInfo的status是StatusEnum类型的,CPInfoDO是String类型,两者类型不一致,不进行属性拷贝赋值。数据库插入时,CPInfoDO的status为null,于是就出现了非空字段插入为空的异常了。
正确用法
BeanCopier beanCopier = BeanCopier.create(CPInfo.class, CPInfoDO.class, false); beanCopier.copy(cpInfo, cpInfoDO, null); // beanCopier复制属性时,属性类型不同不能复制,需要自己set下 cpInfoDO.setStatus(cpInfo.getStatus().getName());
测试关注点
做好单元测试,保证方法输入输出一致
自动化回归测试,并关注后台日志