Browse Source

Merge branch '6.0.x'

# Conflicts:
#	spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
#	spring-core/src/main/java/org/springframework/core/ResolvableType.java
#	spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
pull/31518/head
Juergen Hoeller 2 years ago
parent
commit
93b0b66735
  1. 4
      spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java
  2. 50
      spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
  3. 1
      spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java
  4. 24
      spring-context/src/test/java/org/springframework/context/event/AbstractApplicationEventListenerTests.java
  5. 3
      spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java
  6. 4
      spring-core/src/main/java/org/springframework/core/ResolvableType.java
  7. 2
      spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

4
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 { protected abstract static class PropertyHandler {
@Nullable
private final Class<?> propertyType; private final Class<?> propertyType;
private final boolean readable; private final boolean readable;
private final boolean writable; 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.propertyType = propertyType;
this.readable = readable; this.readable = readable;
this.writable = writable; this.writable = writable;
} }
@Nullable
public Class<?> getPropertyType() { public Class<?> getPropertyType() {
return this.propertyType; return this.propertyType;
} }

50
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.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -613,8 +614,8 @@ public abstract class BeanUtils {
* @return a corresponding MethodParameter object * @return a corresponding MethodParameter object
*/ */
public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) { public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) {
if (pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd) { if (pd instanceof GenericTypeAwarePropertyDescriptor gpd) {
return new MethodParameter(typeAwarePd.getWriteMethodParameter()); return new MethodParameter(gpd.getWriteMethodParameter());
} }
else { else {
Method writeMethod = pd.getWriteMethod(); Method writeMethod = pd.getWriteMethod();
@ -781,38 +782,28 @@ public abstract class BeanUtils {
if (editable != null) { if (editable != null) {
if (!editable.isInstance(target)) { if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() + throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]"); "] not assignable to editable class [" + editable.getName() + "]");
} }
actualEditable = editable; actualEditable = editable;
} }
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null); Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null);
CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ?
CachedIntrospectionResults.forClass(source.getClass()) : null);
for (PropertyDescriptor targetPd : targetPds) { for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod(); Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) { 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) { if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod(); Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) { if (readMethod != null) {
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod); if (isAssignable(writeMethod, 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) {
try { try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { ReflectionUtils.makeAccessible(readMethod);
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source); Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { ReflectionUtils.makeAccessible(writeMethod);
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value); writeMethod.invoke(target, value);
} }
catch (Throwable ex) { 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. * Inner class to avoid a hard dependency on Kotlin at runtime.
@ -899,7 +908,6 @@ public abstract class BeanUtils {
} }
return kotlinConstructor.callBy(argParameters); return kotlinConstructor.callBy(argParameters);
} }
} }
} }

1
spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java

@ -144,7 +144,6 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
ReflectionUtils.makeAccessible(this.field); ReflectionUtils.makeAccessible(this.field);
return this.field.get(getWrappedInstance()); return this.field.get(getWrappedInstance());
} }
catch (IllegalAccessException ex) { catch (IllegalAccessException ex) {
throw new InvalidPropertyException(getWrappedClass(), throw new InvalidPropertyException(getWrappedClass(),
this.field.getName(), "Field is not accessible", ex); this.field.getName(), "Field is not accessible", ex);

24
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,6 +36,10 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
protected static class GenericTestEvent<T> extends ApplicationEvent { protected static class GenericTestEvent<T> extends ApplicationEvent {
@ -51,6 +55,7 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
protected static class SmartGenericTestEvent<T> extends GenericTestEvent<T> implements ResolvableTypeProvider { protected static class SmartGenericTestEvent<T> extends GenericTestEvent<T> implements ResolvableTypeProvider {
private final ResolvableType resolvableType; private final ResolvableType resolvableType;
@ -67,6 +72,7 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
protected static class StringEvent extends GenericTestEvent<String> { protected static class StringEvent extends GenericTestEvent<String> {
public StringEvent(Object source, String payload) { public StringEvent(Object source, String payload) {
@ -74,6 +80,7 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
protected static class LongEvent extends GenericTestEvent<Long> { protected static class LongEvent extends GenericTestEvent<Long> {
public LongEvent(Object source, Long payload) { public LongEvent(Object source, Long payload) {
@ -81,31 +88,31 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> { static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> {
@Override @Override
public void onApplicationEvent(GenericTestEvent<?> event) { public void onApplicationEvent(GenericTestEvent<?> event) {
} }
} }
static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> { static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> {
@Override @Override
public void onApplicationEvent(GenericTestEvent<Object> event) { public void onApplicationEvent(GenericTestEvent<Object> event) {
} }
} }
static class UpperBoundEventListener
implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> { static class UpperBoundEventListener implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
@Override @Override
public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) { public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) {
} }
} }
static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> { static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> {
@Override @Override
@ -113,6 +120,7 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
static class RawApplicationListener implements ApplicationListener { static class RawApplicationListener implements ApplicationListener {
@ -121,10 +129,10 @@ public abstract class AbstractApplicationEventListenerTests {
} }
} }
static class TestEvents { static class TestEvents {
public GenericTestEvent<?> wildcardEvent; public GenericTestEvent<?> wildcardEvent;
} }
} }

3
spring-context/src/test/java/org/springframework/context/event/GenericApplicationListenerAdapterTests.java

@ -104,7 +104,8 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test @Test
public void genericListenerStrictTypeSubClass() { public void genericListenerStrictTypeSubClass() {
supportsEventType(false, ObjectEventListener.class, ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class)); supportsEventType(false, ObjectEventListener.class,
ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
} }
@Test @Test

4
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 * 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 * access a specific generic consider using the {@link #getGeneric(int...)} method as
* it allows access to nested generics and protects against * it allows access to nested generics and protects against
* {@code IndexOutOfBoundsExceptions}. * {@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}) * (never {@code null})
* @see #hasGenerics() * @see #hasGenerics()
* @see #getGeneric(int...) * @see #getGeneric(int...)

2
spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

@ -360,7 +360,7 @@ class ResolvableTypeTests {
ResolvableType type = ResolvableType.forField(field); ResolvableType type = ResolvableType.forField(field);
assertThat(type.isArray()).isTrue(); assertThat(type.isArray()).isTrue();
assertThat(type.getComponentType().getType()) assertThat(type.getComponentType().getType())
.isEqualTo(((Class) field.getGenericType()).componentType()); .isEqualTo(((Class) field.getGenericType()).componentType());
} }
@Test @Test

Loading…
Cancel
Save