From 89ee0b077f7cc16441e056200257a54374dc2545 Mon Sep 17 00:00:00 2001 From: Kunal Patel Date: Thu, 2 Jan 2020 19:22:09 +0530 Subject: [PATCH] Honor generic type information in BeanUtils.copyProperties() Prior to this commit, BeanUtils.copyProperties() ignored generic type information when comparing candidate source and target property types. This commit reworks the implementation of BeanUtils.copyProperties() so that generic type information is taken into account when copying properties. See gh-24281 --- .../org/springframework/beans/BeanUtils.java | 17 +++++-- .../springframework/beans/BeanUtilsTests.java | 44 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index c0ab530a40..a6730b3243 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -19,6 +19,7 @@ package org.springframework.beans; import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -45,6 +46,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -681,7 +683,8 @@ public abstract class BeanUtils { } /** - * Copy the property values of the given source bean into the given target bean. + * Copy the property values of the given source bean into the given target bean + * and ignored if *

Note: The source and target classes do not have to match or even be derived * from each other, as long as the properties match. Any bean properties that the * source bean exposes but the target bean does not will silently be ignored. @@ -714,9 +717,17 @@ public abstract class BeanUtils { if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null) { + Field sourcefield = ReflectionUtils.findField(source.getClass(), sourcePd.getName()); + Field targetfield = ReflectionUtils.findField(target.getClass(), targetPd.getName()); + + TypeDescriptor sourceTypeDescriptor = new TypeDescriptor(sourcefield); + TypeDescriptor targetTypeDescriptor = new TypeDescriptor(targetfield); + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null && - ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { + ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) && + sourceTypeDescriptor.getResolvableType().equals(targetTypeDescriptor.getResolvableType())) { try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); @@ -724,7 +735,7 @@ public abstract class BeanUtils { Object value = readMethod.invoke(source); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); - } + } writeMethod.invoke(target, value); } catch (Throwable ex) { diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java index 907cbbb9b2..50dd7a3c74 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java @@ -24,6 +24,7 @@ import java.net.URI; import java.net.URL; import java.time.DayOfWeek; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; @@ -336,6 +337,49 @@ class BeanUtilsTests { private void assertSignatureEquals(Method desiredMethod, String signature) { assertThat(BeanUtils.resolveSignature(signature, MethodSignatureBean.class)).isEqualTo(desiredMethod); } + + @Test + void testCopiedParametersType() { + + A a = new A(); + a.getList().add(42); + B b = new B(); + + BeanUtils.copyProperties(a, b); + + assertThat(a.getList()).containsOnly(42); + + b.getList().forEach(n -> assertThat(n).isInstanceOf(Long.class)); + assertThat(b.getList()).isEmpty(); + + } + + class A { + + private List list = new ArrayList<>(); + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + } + class B { + + private List list = new ArrayList<>(); + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + } @SuppressWarnings("unused")