Browse Source

TypeDescriptor/ResolvableType cache in GenericTypeAwarePropertyDescriptor

Closes gh-31490
pull/30079/head
Juergen Hoeller 1 year ago
parent
commit
83870e35d1
  1. 10
      spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
  2. 39
      spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
  3. 18
      spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
  4. 40
      spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java
  5. 75
      spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

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

@ -799,7 +799,7 @@ public abstract class BeanUtils { @@ -799,7 +799,7 @@ public abstract class BeanUtils {
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) {
if (isAssignable(writeMethod, readMethod)) {
if (isAssignable(writeMethod, readMethod, sourcePd, targetPd)) {
try {
ReflectionUtils.makeAccessible(readMethod);
Object value = readMethod.invoke(source);
@ -817,7 +817,9 @@ public abstract class BeanUtils { @@ -817,7 +817,9 @@ public abstract class BeanUtils {
}
}
private static boolean isAssignable(Method writeMethod, Method readMethod) {
private static boolean isAssignable(Method writeMethod, Method readMethod,
PropertyDescriptor sourcePd, PropertyDescriptor targetPd) {
Type paramType = writeMethod.getGenericParameterTypes()[0];
if (paramType instanceof Class<?> clazz) {
return ClassUtils.isAssignable(clazz, readMethod.getReturnType());
@ -826,8 +828,8 @@ public abstract class BeanUtils { @@ -826,8 +828,8 @@ public abstract class BeanUtils {
return true;
}
else {
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0);
ResolvableType sourceType = ((GenericTypeAwarePropertyDescriptor) sourcePd).getReadMethodType();
ResolvableType targetType = ((GenericTypeAwarePropertyDescriptor) targetPd).getWriteMethodType();
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :

39
spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java

@ -20,9 +20,9 @@ import java.beans.PropertyDescriptor; @@ -20,9 +20,9 @@ import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
@ -183,23 +183,15 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements @@ -183,23 +183,15 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
"No property '" + propertyName + "' found");
}
TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
if (td == null) {
td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
}
TypeDescriptor td = ((GenericTypeAwarePropertyDescriptor) pd).getTypeDescriptor();
return convertForProperty(propertyName, null, value, td);
}
private Property property(PropertyDescriptor pd) {
GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
}
@Override
@Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null);
return (pd != null ? new BeanPropertyHandler((GenericTypeAwarePropertyDescriptor) pd) : null);
}
@Override
@ -234,58 +226,55 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements @@ -234,58 +226,55 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd;
private final TypeDescriptor typeDescriptor;
private final GenericTypeAwarePropertyDescriptor pd;
public BeanPropertyHandler(PropertyDescriptor pd) {
public BeanPropertyHandler(GenericTypeAwarePropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd;
this.typeDescriptor = new TypeDescriptor(property(pd));
}
@Override
public TypeDescriptor toTypeDescriptor() {
return this.typeDescriptor;
return this.pd.getTypeDescriptor();
}
@Override
public ResolvableType getResolvableType() {
return this.typeDescriptor.getResolvableType();
return this.pd.getReadMethodType();
}
@Override
public TypeDescriptor getMapValueType(int nestingLevel) {
return new TypeDescriptor(
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asMap().getGeneric(1),
null, this.typeDescriptor.getAnnotations());
this.pd.getReadMethodType().getNested(nestingLevel).asMap().getGeneric(1),
null, this.pd.getTypeDescriptor().getAnnotations());
}
@Override
public TypeDescriptor getCollectionType(int nestingLevel) {
return new TypeDescriptor(
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asCollection().getGeneric(),
null, this.typeDescriptor.getAnnotations());
this.pd.getReadMethodType().getNested(nestingLevel).asCollection().getGeneric(),
null, this.pd.getTypeDescriptor().getAnnotations());
}
@Override
@Nullable
public TypeDescriptor nested(int level) {
return TypeDescriptor.nested(property(this.pd), level);
return this.pd.getTypeDescriptor().nested(level);
}
@Override
@Nullable
public Object getValue() throws Exception {
Method readMethod = this.pd.getReadMethod();
Assert.state(readMethod != null, "No read method available");
ReflectionUtils.makeAccessible(readMethod);
return readMethod.invoke(getWrappedInstance(), (Object[]) null);
}
@Override
public void setValue(@Nullable Object value) throws Exception {
Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd ?
typeAwarePd.getWriteMethodForActualAccess() : this.pd.getWriteMethod());
Method writeMethod = this.pd.getWriteMethodForActualAccess();
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(getWrappedInstance(), value);
}

18
spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@ -35,7 +35,6 @@ import java.util.concurrent.ConcurrentMap; @@ -35,7 +35,6 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@ -235,9 +234,6 @@ public final class CachedIntrospectionResults { @@ -235,9 +234,6 @@ public final class CachedIntrospectionResults {
/** PropertyDescriptor objects keyed by property name String. */
private final Map<String, PropertyDescriptor> propertyDescriptors;
/** TypeDescriptor objects keyed by PropertyDescriptor. */
private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;
/**
* Create a new CachedIntrospectionResults instance for the given class.
@ -300,8 +296,6 @@ public final class CachedIntrospectionResults { @@ -300,8 +296,6 @@ public final class CachedIntrospectionResults {
// - accessor method directly referring to instance field of same name
// - same convention for component accessors of Java 15 record classes
introspectPlainAccessors(beanClass, readMethodNames);
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
}
catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
@ -410,14 +404,4 @@ public final class CachedIntrospectionResults { @@ -410,14 +404,4 @@ public final class CachedIntrospectionResults {
}
}
TypeDescriptor addTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) {
TypeDescriptor existing = this.typeDescriptorCache.putIfAbsent(pd, td);
return (existing != null ? existing : td);
}
@Nullable
TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) {
return this.typeDescriptorCache.get(pd);
}
}

40
spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java

@ -26,8 +26,10 @@ import java.util.Set; @@ -26,8 +26,10 @@ import java.util.Set;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -57,6 +59,15 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { @@ -57,6 +59,15 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
@Nullable
private MethodParameter writeMethodParameter;
@Nullable
private volatile ResolvableType writeMethodType;
@Nullable
private ResolvableType readMethodType;
@Nullable
private volatile TypeDescriptor typeDescriptor;
@Nullable
private Class<?> propertyType;
@ -107,7 +118,8 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { @@ -107,7 +118,8 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
}
if (this.readMethod != null) {
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
this.readMethodType = ResolvableType.forMethodReturnType(this.readMethod, this.beanClass);
this.propertyType = this.readMethodType.resolve(this.readMethod.getReturnType());
}
else if (this.writeMethodParameter != null) {
this.propertyType = this.writeMethodParameter.getParameterType();
@ -150,6 +162,30 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { @@ -150,6 +162,30 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
return this.writeMethodParameter;
}
public ResolvableType getWriteMethodType() {
ResolvableType writeMethodType = this.writeMethodType;
if (writeMethodType == null) {
writeMethodType = ResolvableType.forMethodParameter(getWriteMethodParameter());
this.writeMethodType = writeMethodType;
}
return writeMethodType;
}
public ResolvableType getReadMethodType() {
Assert.state(this.readMethodType != null, "No read method available");
return this.readMethodType;
}
public TypeDescriptor getTypeDescriptor() {
TypeDescriptor typeDescriptor = this.typeDescriptor;
if (typeDescriptor == null) {
Property property = new Property(getBeanClass(), getReadMethod(), getWriteMethod(), getName());
typeDescriptor = new TypeDescriptor(property);
this.typeDescriptor = typeDescriptor;
}
return typeDescriptor;
}
@Override
@Nullable
public Class<?> getPropertyType() {

75
spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

@ -170,6 +170,33 @@ public class TypeDescriptor implements Serializable { @@ -170,6 +170,33 @@ public class TypeDescriptor implements Serializable {
return this.resolvableType.getSource();
}
/**
* Create a type descriptor for a nested type declared within this descriptor.
* @param nestingLevel the nesting level of the collection/array element or
* map key/value declaration within the property
* @return the nested type descriptor at the specified nesting level, or
* {@code null} if it could not be obtained
* @since 6.1
*/
@Nullable
public TypeDescriptor nested(int nestingLevel) {
ResolvableType nested = this.resolvableType;
for (int i = 0; i < nestingLevel; i++) {
if (Object.class == nested.getType()) {
// Could be a collection type but we don't know about its element type,
// so let's just assume there is an element type of type Object...
}
else {
nested = nested.getNested(2);
}
}
if (nested == ResolvableType.NONE) {
return null;
}
return getRelatedIfResolvable(nested);
}
/**
* Narrows this {@link TypeDescriptor} by setting its type to the class of the
* provided value.
@ -335,9 +362,9 @@ public class TypeDescriptor implements Serializable { @@ -335,9 +362,9 @@ public class TypeDescriptor implements Serializable {
return new TypeDescriptor(getResolvableType().getComponentType(), null, getAnnotations());
}
if (Stream.class.isAssignableFrom(getType())) {
return getRelatedIfResolvable(this, getResolvableType().as(Stream.class).getGeneric(0));
return getRelatedIfResolvable(getResolvableType().as(Stream.class).getGeneric(0));
}
return getRelatedIfResolvable(this, getResolvableType().asCollection().getGeneric(0));
return getRelatedIfResolvable(getResolvableType().asCollection().getGeneric(0));
}
/**
@ -380,7 +407,7 @@ public class TypeDescriptor implements Serializable { @@ -380,7 +407,7 @@ public class TypeDescriptor implements Serializable {
@Nullable
public TypeDescriptor getMapKeyTypeDescriptor() {
Assert.state(isMap(), "Not a [java.util.Map]");
return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(0));
return getRelatedIfResolvable(getResolvableType().asMap().getGeneric(0));
}
/**
@ -417,7 +444,7 @@ public class TypeDescriptor implements Serializable { @@ -417,7 +444,7 @@ public class TypeDescriptor implements Serializable {
@Nullable
public TypeDescriptor getMapValueTypeDescriptor() {
Assert.state(isMap(), "Not a [java.util.Map]");
return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(1));
return getRelatedIfResolvable(getResolvableType().asMap().getGeneric(1));
}
/**
@ -442,6 +469,14 @@ public class TypeDescriptor implements Serializable { @@ -442,6 +469,14 @@ public class TypeDescriptor implements Serializable {
return narrow(mapValue, getMapValueTypeDescriptor());
}
@Nullable
private TypeDescriptor getRelatedIfResolvable(ResolvableType type) {
if (type.resolve() == null) {
return null;
}
return new TypeDescriptor(type, null, getAnnotations());
}
@Nullable
private TypeDescriptor narrow(@Nullable Object value, @Nullable TypeDescriptor typeDescriptor) {
if (typeDescriptor != null) {
@ -645,7 +680,7 @@ public class TypeDescriptor implements Serializable { @@ -645,7 +680,7 @@ public class TypeDescriptor implements Serializable {
throw new IllegalArgumentException("MethodParameter nesting level must be 1: " +
"use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal");
}
return nested(new TypeDescriptor(methodParameter), nestingLevel);
return new TypeDescriptor(methodParameter).nested(nestingLevel);
}
/**
@ -671,7 +706,7 @@ public class TypeDescriptor implements Serializable { @@ -671,7 +706,7 @@ public class TypeDescriptor implements Serializable {
*/
@Nullable
public static TypeDescriptor nested(Field field, int nestingLevel) {
return nested(new TypeDescriptor(field), nestingLevel);
return new TypeDescriptor(field).nested(nestingLevel);
}
/**
@ -697,33 +732,7 @@ public class TypeDescriptor implements Serializable { @@ -697,33 +732,7 @@ public class TypeDescriptor implements Serializable {
*/
@Nullable
public static TypeDescriptor nested(Property property, int nestingLevel) {
return nested(new TypeDescriptor(property), nestingLevel);
}
@Nullable
private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
ResolvableType nested = typeDescriptor.resolvableType;
for (int i = 0; i < nestingLevel; i++) {
if (Object.class == nested.getType()) {
// Could be a collection type but we don't know about its element type,
// so let's just assume there is an element type of type Object...
}
else {
nested = nested.getNested(2);
}
}
if (nested == ResolvableType.NONE) {
return null;
}
return getRelatedIfResolvable(typeDescriptor, nested);
}
@Nullable
private static TypeDescriptor getRelatedIfResolvable(TypeDescriptor source, ResolvableType type) {
if (type.resolve() == null) {
return null;
}
return new TypeDescriptor(type, null, source.getAnnotations());
return new TypeDescriptor(property).nested(nestingLevel);
}

Loading…
Cancel
Save