diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index 38274e8c7ff..fcb43011290 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -1013,18 +1013,20 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA */ protected abstract static class PropertyHandler { + @Nullable private final Class propertyType; private final boolean readable; private final boolean writable; - public PropertyHandler(Class propertyType, boolean readable, boolean writable) { + public PropertyHandler(@Nullable Class propertyType, boolean readable, boolean writable) { this.propertyType = propertyType; this.readable = readable; this.writable = writable; } + @Nullable public Class getPropertyType() { return this.propertyType; } 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 448b8bc3c36..ec2a7c70c0e 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.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -613,8 +614,8 @@ public abstract class BeanUtils { * @return a corresponding MethodParameter object */ public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) { - if (pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd) { - return new MethodParameter(typeAwarePd.getWriteMethodParameter()); + if (pd instanceof GenericTypeAwarePropertyDescriptor gpd) { + return new MethodParameter(gpd.getWriteMethodParameter()); } else { Method writeMethod = pd.getWriteMethod(); @@ -781,38 +782,28 @@ public abstract class BeanUtils { 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); 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 && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) { - PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), 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) { @@ -826,6 +817,24 @@ public abstract class BeanUtils { } } + private static boolean isAssignable(Method writeMethod, Method readMethod) { + Type paramType = writeMethod.getGenericParameterTypes()[0]; + if (paramType instanceof Class clazz) { + return ClassUtils.isAssignable(clazz, 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. @@ -899,7 +908,6 @@ public abstract class BeanUtils { } return kotlinConstructor.callBy(argParameters); } - } } diff --git a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index 3a97b43f77d..145a5cff991 100644 --- a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -144,7 +144,6 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor { ReflectionUtils.makeAccessible(this.field); return this.field.get(getWrappedInstance()); } - catch (IllegalAccessException ex) { throw new InvalidPropertyException(getWrappedClass(), this.field.getName(), "Field is not accessible", ex); diff --git a/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java index bf4360ef589..22d33a109cb 100644 --- a/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,10 @@ public abstract class AbstractApplicationEventListenerTests { } } + protected GenericTestEvent createGenericTestEvent(T payload) { + return new GenericTestEvent<>(this, payload); + } + protected static class GenericTestEvent extends ApplicationEvent { @@ -51,6 +55,7 @@ public abstract class AbstractApplicationEventListenerTests { } } + protected static class SmartGenericTestEvent extends GenericTestEvent implements ResolvableTypeProvider { private final ResolvableType resolvableType; @@ -67,6 +72,7 @@ public abstract class AbstractApplicationEventListenerTests { } } + protected static class StringEvent extends GenericTestEvent { public StringEvent(Object source, String payload) { @@ -74,6 +80,7 @@ public abstract class AbstractApplicationEventListenerTests { } } + protected static class LongEvent extends GenericTestEvent { public LongEvent(Object source, Long payload) { @@ -81,31 +88,31 @@ public abstract class AbstractApplicationEventListenerTests { } } - protected GenericTestEvent createGenericTestEvent(T payload) { - return new GenericTestEvent<>(this, payload); - } - static class GenericEventListener implements ApplicationListener> { + @Override public void onApplicationEvent(GenericTestEvent event) { } } + static class ObjectEventListener implements ApplicationListener> { + @Override public void onApplicationEvent(GenericTestEvent event) { } } - static class UpperBoundEventListener - implements ApplicationListener> { + + static class UpperBoundEventListener implements ApplicationListener> { @Override public void onApplicationEvent(GenericTestEvent event) { } } + static class StringEventListener implements ApplicationListener> { @Override @@ -113,6 +120,7 @@ public abstract class AbstractApplicationEventListenerTests { } } + @SuppressWarnings("rawtypes") static class RawApplicationListener implements ApplicationListener { @@ -121,10 +129,10 @@ public abstract class AbstractApplicationEventListenerTests { } } + static class TestEvents { public GenericTestEvent wildcardEvent; - } } diff --git a/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java b/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java index ceae6db126b..6fdb68bb0af 100644 --- a/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java @@ -104,7 +104,8 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE @Test public void genericListenerStrictTypeSubClass() { - supportsEventType(false, ObjectEventListener.class, ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class)); + supportsEventType(false, ObjectEventListener.class, + ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class)); } @Test diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 97a346fbac8..6db94aa366d 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -714,12 +714,12 @@ public class ResolvableType implements Serializable { } /** - * Return an array of {@link ResolvableType ResolvableTypes} representing the generic parameters of + * Return an array of {@code ResolvableType ResolvableTypes} representing the generic parameters of * this type. If no generics are available an empty array is returned. If you need to * access a specific generic consider using the {@link #getGeneric(int...)} method as * it allows access to nested generics and protects against * {@code IndexOutOfBoundsExceptions}. - * @return an array of {@link ResolvableType ResolvableTypes} representing the generic parameters + * @return an array of {@code ResolvableType ResolvableTypes} representing the generic parameters * (never {@code null}) * @see #hasGenerics() * @see #getGeneric(int...) diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 424b27388f0..a1388be50d9 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -360,7 +360,7 @@ class ResolvableTypeTests { ResolvableType type = ResolvableType.forField(field); assertThat(type.isArray()).isTrue(); assertThat(type.getComponentType().getType()) - .isEqualTo(((Class) field.getGenericType()).componentType()); + .isEqualTo(((Class) field.getGenericType()).componentType()); } @Test