Spring中使用BeanUtils.copyProperties()导致Hessian/Burlap:ClassNotFoundException
背景
遇到一个问题:
dubbo服务客户端发现提示警告异常
[New I/O worker #4] WARN c.a.c.c.hessian.io.SerializerFactory - Hessian/Burlap: 'XX.XX.XBean' is an unknown class in java.net.URLClassLoader@988246e:
java.lang.ClassNotFoundException: XX.XX.XBean
但是根据代码查到该XBean并没有通过dubbo服务,而是转换为了DTO返回
那么为啥会报这个找不到呢
原因:
BeanUtils 是 org.springframework.beans 包下的类。
BeanUtils.copyProperties,是浅拷贝,引用类型拷贝的是地址。
转换DTO的时候使用的是BeanUtils.copyProperties,浅拷贝,Spring在5.3.0版本之前这个方法的源码中没有泛型的判断,所以通过反射的方法赋值会出现实际的类型与声明的不一致。
比如:List<Child> 拷贝给 List<Parent>,即使范型类型不一致,也能拷贝成功。hession序列化的时候会访问到XBean的引用,由于客户端并不依赖XBean,所以找不到。
源码解析
BeanUtils.copyProperties()源码解析
上面所说的常见的「坑」,翻看Spring的BeanUtils.copyProperties()方法源码,就很容易发现问题出现的原因。
源码核心部分(5.3.8版本):
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException { // ... 其他 Class<?> actualEditable = target.getClass(); //... 其他 // 获取目标类的所有属性描述(PropertyDescriptor,主要包括属性名称和其相关的读写方法即set、get方法) PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); // ... 其他 // 遍历所有属性,为每个属性赋值 for (PropertyDescriptor targetPd : targetPds) { // 获取属性的set方法 Method writeMethod = targetPd.getWriteMethod(); if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { // 获取targetBean属性对应sourceBean的属性描述 PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null) { // sourceBean属性的读方法,即get方法 Method readMethod = sourcePd.getReadMethod(); if(readMethod != null) { ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod); ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0); // 判断属性类型是否一致,包括泛型是否一致 boolean isAssignable = (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ? ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : targetResolvableType.isAssignableFrom(sourceResolvableType)); if (isAssignable) { try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } // 通过反射,调用get方法,获取source属性的值 Object value = readMethod.invoke(source); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } // 通过反射,调用set方法,将source属性的值赋值给target的属性 writeMethod.invoke(target, value); } catch (Throwable ex) { throw new FatalBeanException( "Could not copy property '" + targetPd.getName() + "' from source to target", ex); } } } } } }
从源码可以看出:
-
sourceBean和targetBean的属性的拷贝,是通过反射中的Method完成的,所以如果Bean不声明属性的set和get方法,则不能属性间的copy。
-
Method的invoke方法,只是把sourceBean的get方法获取的值通过targetBean的set方法设置,所以并不涉及深拷贝,只是拷贝属性的引用。
-
上面的源码,有一步:属性的泛型是否一致判断。
boolean isAssignable = (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ? ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : targetResolvableType.isAssignableFrom(sourceResolvableType));
而在Spring 5.3.0之前并没有这一步,其判断方式:
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()))
- 在5.3.0之前没有泛型的判断,所以通过反射的方法赋值会出现实际的类型与声明的不一致。
本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。
出处:http://www.cnblogs.com/lingyejun/
若本文如对您有帮助,不妨点击一下右下角的【推荐】。
如果您喜欢或希望看到更多我的文章,可扫描二维码关注我的微信公众号《翎野君》。
转载文章请务必保留出处和署名,否则保留追究法律责任的权利。