气死!BeanCopy怎么copy出来个不认识的类!
前言
前段时间在调用rpc
的时候,发现rpc
返回结果中存在一个没有被定义的类,简单来说就是响应体Class
并没有定义这个类,但它却结结实实的出现在响应中,这就离了个大谱!
于是我直接clone
下目标project
,直接翻rpc
一探究竟!
场景复现
这里就不扯东扯西了,看文章标题就知道,最后是BeanUtils
搞的鬼,下面我直接用一段代码复现出问题
public class BeanUtilsTest {
public static void main(String[] args) {
User user = new User();
user.setAge(1);
InnerUser innerUser = new InnerUser();
innerUser.setInner("我是属于User的");
user.setList(Lists.newArrayList(innerUser));
Person person = new Person();
// 属性拷贝
BeanUtils.copyProperties(user, person);
System.out.println(person);
}
}
@Data
class User {
private Integer age;
private List<InnerUser> list;
}
@Data
class Person {
private Integer age;
private List<InnerPerson> list;
}
@Data
class InnerUser {
private String inner;
}
@Data
class InnerPerson {
private String inner;
}
假设Person
类就是我的rpc
返回结果的话,此时Person
类中并不存在InnerUser
类,但它却出现了响应结果中,这就离谱,我根本不认识它!
什么原因?
有细心小伙伴会发现我在User和Person
类中,都定义了list
字段,但是User
类中是List<InnerUser>
,而Person
类中是List<InnerPerson> list
,两者泛型不同,应该是不能copy
成功的,但是现在却成功了,而且还把InnerUser
也copy
过来了!
下面来看BeanUtils.copyProperties
源码中对于source
和target
字段的比较逻辑
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
// 获取target类当前字段的写入方法,也就是set方法
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
// 获取source类中该字段的get方法
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
// 如果能获取到,且get方法返回类型与set方法的第一个入参类型相匹配,则可以invoke填充
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 反射填充属性
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
通过上面源码结合下面图示,我们可以清晰的看到,copyProperties
只比较了字段的类型,如果说字段存在泛型
,则并没有去比较,所以上面的案例代码能够copy
成功。

解决办法
升级spring-beans
版本至5.3.27
,在新版本中的BeanUtils
已经解决了这个问题
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
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);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
}
通过源码可见
- 如果
source
具备不可解析的泛型
,则直接为true
source
为可解析泛型
target
为不可解析泛型
,则忽略泛型,直接比较字段原始类型target
为可解析泛型
,则比较source、target
泛型
扩展点
其实如果只是要解决本文所提出的泛型问题,最低升级spring-beans
版本到5.3.0
就可解决问题,但上面为什么说要升级到5.3.27
呢?
因为上面提到了一个概念: 不可解析的泛型
在5.3.0
中已经修改为通过泛型
比较了,但是由于泛型擦除
机制存在,泛型擦除后,一切归为原始类型
,即不可解析泛型
,直接通过isAssignableFrom
判断可能会不太精确
所以如上面小结所见, 在5.3.27
版本中,当source、target
都为不可解析泛型时,还是选择用字段原始类型
进行比较
总结
有关BeanUtils
的泛型问题虽然大部分同学都知道一些,但是可能实际业务开发过程中并不会遇到,本文也是由于项目所使用的spring
版本不是很新,才会让我在开发过程中遇到这个问题,故梳理出本文稍微总结一下~
最后,我是 Code皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步!
觉得文章不错的话,希望大家多多支持,一键三连~ 页可以在 掘金 关注我~
转载自:https://juejin.cn/post/7249740342751166525