Skip to content

Commit 89ee0b0

Browse files
Kunal Patelsbrannen
Kunal Patel
authored andcommitted
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
1 parent cdde19c commit 89ee0b0

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.beans.PropertyDescriptor;
2020
import java.beans.PropertyEditor;
2121
import java.lang.reflect.Constructor;
22+
import java.lang.reflect.Field;
2223
import java.lang.reflect.InvocationTargetException;
2324
import java.lang.reflect.Method;
2425
import java.lang.reflect.Modifier;
@@ -45,6 +46,7 @@
4546

4647
import org.springframework.core.KotlinDetector;
4748
import org.springframework.core.MethodParameter;
49+
import org.springframework.core.convert.TypeDescriptor;
4850
import org.springframework.lang.Nullable;
4951
import org.springframework.util.Assert;
5052
import org.springframework.util.ClassUtils;
@@ -681,7 +683,8 @@ public static void copyProperties(Object source, Object target, String... ignore
681683
}
682684

683685
/**
684-
* Copy the property values of the given source bean into the given target bean.
686+
* Copy the property values of the given source bean into the given target bean
687+
* and ignored if
685688
* <p>Note: The source and target classes do not have to match or even be derived
686689
* from each other, as long as the properties match. Any bean properties that the
687690
* source bean exposes but the target bean does not will silently be ignored.
@@ -714,17 +717,25 @@ private static void copyProperties(Object source, Object target, @Nullable Class
714717
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
715718
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
716719
if (sourcePd != null) {
720+
Field sourcefield = ReflectionUtils.findField(source.getClass(), sourcePd.getName());
721+
Field targetfield = ReflectionUtils.findField(target.getClass(), targetPd.getName());
722+
723+
TypeDescriptor sourceTypeDescriptor = new TypeDescriptor(sourcefield);
724+
TypeDescriptor targetTypeDescriptor = new TypeDescriptor(targetfield);
725+
717726
Method readMethod = sourcePd.getReadMethod();
727+
718728
if (readMethod != null &&
719-
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
729+
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) &&
730+
sourceTypeDescriptor.getResolvableType().equals(targetTypeDescriptor.getResolvableType())) {
720731
try {
721732
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
722733
readMethod.setAccessible(true);
723734
}
724735
Object value = readMethod.invoke(source);
725736
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
726737
writeMethod.setAccessible(true);
727-
}
738+
}
728739
writeMethod.invoke(target, value);
729740
}
730741
catch (Throwable ex) {

spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.net.URL;
2525
import java.time.DayOfWeek;
2626
import java.time.LocalDateTime;
27+
import java.util.ArrayList;
2728
import java.util.Date;
2829
import java.util.List;
2930
import java.util.Locale;
@@ -336,6 +337,49 @@ void isNotSimpleProperty(Class<?> type) {
336337
private void assertSignatureEquals(Method desiredMethod, String signature) {
337338
assertThat(BeanUtils.resolveSignature(signature, MethodSignatureBean.class)).isEqualTo(desiredMethod);
338339
}
340+
341+
@Test
342+
void testCopiedParametersType() {
343+
344+
A a = new A();
345+
a.getList().add(42);
346+
B b = new B();
347+
348+
BeanUtils.copyProperties(a, b);
349+
350+
assertThat(a.getList()).containsOnly(42);
351+
352+
b.getList().forEach(n -> assertThat(n).isInstanceOf(Long.class));
353+
assertThat(b.getList()).isEmpty();
354+
355+
}
356+
357+
class A {
358+
359+
private List<Integer> list = new ArrayList<>();
360+
361+
public List<Integer> getList() {
362+
return list;
363+
}
364+
365+
public void setList(List<Integer> list) {
366+
this.list = list;
367+
}
368+
369+
}
370+
class B {
371+
372+
private List<Long> list = new ArrayList<>();
373+
374+
public List<Long> getList() {
375+
return list;
376+
}
377+
378+
public void setList(List<Long> list) {
379+
this.list = list;
380+
}
381+
382+
}
339383

340384

341385
@SuppressWarnings("unused")

0 commit comments

Comments
 (0)