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
This commit is contained in:
Kunal Patel 2020-01-02 19:22:09 +05:30 committed by Sam Brannen
parent cdde19c0bc
commit 89ee0b077f
2 changed files with 58 additions and 3 deletions

View File

@ -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
* <p>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) {

View File

@ -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<Integer> list = new ArrayList<>();
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
}
class B {
private List<Long> list = new ArrayList<>();
public List<Long> getList() {
return list;
}
public void setList(List<Long> list) {
this.list = list;
}
}
@SuppressWarnings("unused")