Spring工具 BeanCopier异常问题
我们经常遇到相同属性的不同类时,为了代码简洁性和可复用性,当然最重要的是节省时间,我们会用到复制这个功能,一般有普遍用的有spring、apache-commons、net.sf.cglib。当然如果时间允许可以基于反射实现此功能,下次带大家看下两种复制的性能比较。现在大多数项目用spring框架,所以本文主要来告诉大家使用spring的BeanCopier所遇到的坑。
坑1:抛出NullPointerException异常
大家看到此异常第一步就想自己是不是原实例是不是空,但是本猿仔细检查了下,有实例化。具体错误如下:
Caused by: java.lang.NullPointerException
at org.springframework.cglib.core.ReflectUtils.getMethodInfo(ReflectUtils.java:424)
at org.springframework.cglib.beans.BeanCopier$Generator.generateClass(BeanCopier.java:133)
at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at org.springframework.cglib.beans.BeanCopier$Generator.create(BeanCopier.java:90)
at org.springframework.cglib.beans.BeanCopier.create(BeanCopier.java:50)
at com.xx.framework.commons.utils.BeanCopierUtils.copyProperties(BeanCopierUtils.java:48)
at com.xx.framework.commons.utils.BeanCopierUtils.copyOne2One(BeanCopierUtils.java:77)
来看下用法:
public class BeanCopierUtils {
private static final LoggerAdapter LOGGER;
private static final Map<String, BeanCopier> BEANCOPIER_MAP;
public static final Map<String, BeanCopier> BEAN_COPIER_MAP;
static {
BEANCOPIER_MAP = new HashMap();
BEAN_COPIER_MAP = new HashMap();
}
private BeanCopierUtils() {
}
public static void copyProperties(Object source, Object target) {
if (source != null && target != null) {
String beanKey = generateKey(source.getClass(), target.getClass());
BeanCopier copier = (BeanCopier)BEAN_COPIER_MAP.get(beanKey);
if (copier == null) {
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
BEAN_COPIER_MAP.put(beanKey, copier);
}
copier.copy(source, target, (Converter)null);
}
}
private static String generateKey(Class<?> class1, Class<?> class2) {
return class1.toString() + class2.toString();
}
public static <T> T copyOne2One(Object source, Class<T> target) {
Object instance = target.newInstance();
copyProperties(source, instance);
return instance;
}
public static <T> List<T> copyList2List(List<?> source, Class<T> target) {
if (source.isEmpty()) {
return new ArrayList();
} else {
List<T> result = new ArrayList();
Iterator iter= source.iterator();
while(iter.hasNext()) {
Object obj = iter.next();
result.add(copyOne2One(obj, target));
}
return result;
}
}
public static void copyAttribute(Object source, Object target) {
if (source != null && target != null) {
String key = source.getClass().toString() + target.getClass().toString();
BeanCopier beanCopier = (BeanCopier)BEANCOPIER_MAP.get(key);
if (beanCopier == null) {
beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
BEANCOPIER_MAP.put(key, beanCopier);
}
beanCopier.copy(source, target, (Converter)null);
}
}
}
为了避免每次BeanCopier创建太慢和节省堆空间,这里每个两个类之间转换创建一个实例。复制方法起作用是如下
beanCopier.copy(source, target, (Converter)null);
根据打印的错误信息,我们可以追踪最底层到如下行错误:
通常我们采用AES加密会采用如下方式,
public static MethodInfo getMethodInfo(Member member) {
return getMethodInfo(member, member.getModifiers());
}
这个很明显是member为空,然后往上层走,创建BeanCopier时调用的此方法:
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
settter 结果如下获取:
PropertyDescriptor[] setters = ReflectUtils.getBeanGetters(this.target);
PropertyDescriptor setter = setters[i];
我也比较奇怪为何用getBeanGetters,应该用getBeanSetters的,我们可以追踪到
如何避免
1.加密后返回byte数组转换成16进制字符串或者base64编码字符串,如下
public static PropertyDescriptor[] getBeanGetters(Class type) {
return getPropertiesHelper(type, true, false);
}
private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
try {
BeanInfo info = Introspector.getBeanInfo(type, class$java$lang$Object == null ? (class$java$lang$Object = class$("java.lang.Object")) : class$java$lang$Object);
PropertyDescriptor[] all = info.getPropertyDescriptors();
if (read && write) {
return all;
} else {
List properties = new ArrayList(all.length);
for(int i = 0; i < all.length; ++i) {
PropertyDescriptor pd = all[i];
if (read && pd.getReadMethod() != null || write && pd.getWriteMethod() != null) {
properties.add(pd);
}
}
return (PropertyDescriptor[])((PropertyDescriptor[])properties.toArray(new PropertyDescriptor[properties.size()]));
}
} catch (IntrospectionException var8) {
throw new CodeGenerationException(var8);
}
}
然后PropertyDescriptor.getReadMethod和getWriteMethod方法:
public synchronized Method getReadMethod() {
Method readMethod = this.readMethodRef.get();
if (readMethod == null) {
Class<?> cls = getClass0();
if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) {
// The read method was explicitly set to null.
return null;
}
String nextMethodName = Introspector.GET_PREFIX + getBaseName();
if (readMethodName == null) {
Class<?> type = getPropertyType0();
if (type == boolean.class || type == null) {
readMethodName = Introspector.IS_PREFIX + getBaseName();
} else {
readMethodName = nextMethodName;
}
}
// Since there can be multiple write methods but only one getter
// method, find the getter method first so that you know what the
// property type is. For booleans, there can be "is" and "get"
// methods. If an "is" method exists, this is the official
// reader method so look for this one first.
readMethod = Introspector.findMethod(cls, readMethodName, 0);
if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {
readMethodName = nextMethodName;
readMethod = Introspector.findMethod(cls, readMethodName, 0);
}
try {
setReadMethod(readMethod);
} catch (IntrospectionException ex) {
// fall
}
}
return readMethod;
}
public synchronized Method getWriteMethod() {
Method writeMethod = this.writeMethodRef.get();
if (writeMethod == null) {
Class<?> cls = getClass0();
if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {
// The write method was explicitly set to null.
return null;
}
// We need the type to fetch the correct method.
Class<?> type = getPropertyType0();
if (type == null) {
try {
// Can't use getPropertyType since it will lead to recursive loop.
type = findPropertyType(getReadMethod(), null);
setPropertyType(type);
} catch (IntrospectionException ex) {
// Without the correct property type we can't be guaranteed
// to find the correct method.
return null;
}
}
if (writeMethodName == null) {
writeMethodName = Introspector.SET_PREFIX + getBaseName();
}
Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
if (writeMethod != null) {
if (!writeMethod.getReturnType().equals(void.class)) {
writeMethod = null;
}
}
try {
setWriteMethod(writeMethod);
} catch (IntrospectionException ex) {
// fall through
}
}
return writeMethod;
}
可以看到获取前缀为get或者is的为readMethod,获取无返参的为writeMethod。writeMethod只有返参不为void时才会为空,其实从上面就知道了,我目标类里有个isSuccess方法,而setSuccess方法没有,所以返回为空,就异常了。这里的spring 版本是4.17,经测试返现到4.2.5此bug才修复。
坑2.有时属性值copy不了
这个bug经测试发现当目标类型和源类型的属性一个为基础类型如:int时,另一个为包装类型时:Integer,才有该问题,其他问题都是属性名对应不上
本文深入探讨Spring框架中BeanCopier工具在属性复制时可能遇到的NullPointerException异常及属性复制失败的问题,分析原因并提供解决方案。
1074

被折叠的 条评论
为什么被折叠?



