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 bc0b2de1a19c..81283ac27ce6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import java.net.URI; import java.net.URL; import java.time.temporal.Temporal; @@ -30,6 +31,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -787,38 +789,28 @@ private static void copyProperties(Object source, Object target, @Nullable Class if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + - "] not assignable to Editable class [" + editable.getName() + "]"); + "] not assignable to editable class [" + editable.getName() + "]"); } actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); - List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); + Set ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null); + CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ? + CachedIntrospectionResults.forClass(source.getClass()) : 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 (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) { + PropertyDescriptor sourcePd = (sourceResults != null ? + sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd); if (sourcePd != null) { Method readMethod = sourcePd.getReadMethod(); if (readMethod != null) { - ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod); - ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0); - - // Ignore generic types in assignable check if either ResolvableType has unresolvable generics. - boolean isAssignable = - (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ? - ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : - targetResolvableType.isAssignableFrom(sourceResolvableType)); - - if (isAssignable) { + if (isAssignable(writeMethod, readMethod)) { try { - if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { - readMethod.setAccessible(true); - } + ReflectionUtils.makeAccessible(readMethod); Object value = readMethod.invoke(source); - if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { - writeMethod.setAccessible(true); - } + ReflectionUtils.makeAccessible(writeMethod); writeMethod.invoke(target, value); } catch (Throwable ex) { @@ -832,6 +824,24 @@ private static void copyProperties(Object source, Object target, @Nullable Class } } + private static boolean isAssignable(Method writeMethod, Method readMethod) { + Type paramType = writeMethod.getGenericParameterTypes()[0]; + if (paramType instanceof Class) { + return ClassUtils.isAssignable((Class) paramType, readMethod.getReturnType()); + } + else if (paramType.equals(readMethod.getGenericReturnType())) { + return true; + } + else { + ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod); + ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0); + // Ignore generic types in assignable check if either ResolvableType has unresolvable generics. + return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ? + ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : + targetType.isAssignableFrom(sourceType)); + } + } + /** * Inner class to avoid a hard dependency on Kotlin at runtime. @@ -892,7 +902,6 @@ public static T instantiateClass(Constructor ctor, Object... args) } return kotlinConstructor.callBy(argParameters); } - } }