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 {
if (sourcePd != null) { if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod(); Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) { if (readMethod != null) {
if (isAssignable(writeMethod, readMethod)) { if (isAssignable(writeMethod, readMethod, sourcePd, targetPd)) {
try { try {
ReflectionUtils.makeAccessible(readMethod); ReflectionUtils.makeAccessible(readMethod);
Object value = readMethod.invoke(source); Object value = readMethod.invoke(source);
@ -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]; Type paramType = writeMethod.getGenericParameterTypes()[0];
if (paramType instanceof Class<?> clazz) { if (paramType instanceof Class<?> clazz) {
return ClassUtils.isAssignable(clazz, readMethod.getReturnType()); return ClassUtils.isAssignable(clazz, readMethod.getReturnType());
@ -826,8 +828,8 @@ public abstract class BeanUtils {
return true; return true;
} }
else { else {
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod); ResolvableType sourceType = ((GenericTypeAwarePropertyDescriptor) sourcePd).getReadMethodType();
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0); ResolvableType targetType = ((GenericTypeAwarePropertyDescriptor) targetPd).getWriteMethodType();
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics. // Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ? return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : 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;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
@ -183,23 +183,15 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
"No property '" + propertyName + "' found"); "No property '" + propertyName + "' found");
} }
TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); TypeDescriptor td = ((GenericTypeAwarePropertyDescriptor) pd).getTypeDescriptor();
if (td == null) {
td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
}
return convertForProperty(propertyName, null, value, td); 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 @Override
@Nullable @Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) { protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null); return (pd != null ? new BeanPropertyHandler((GenericTypeAwarePropertyDescriptor) pd) : null);
} }
@Override @Override
@ -234,58 +226,55 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
private class BeanPropertyHandler extends PropertyHandler { private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd; private final GenericTypeAwarePropertyDescriptor pd;
private final TypeDescriptor typeDescriptor;
public BeanPropertyHandler(PropertyDescriptor pd) { public BeanPropertyHandler(GenericTypeAwarePropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null); super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd; this.pd = pd;
this.typeDescriptor = new TypeDescriptor(property(pd));
} }
@Override @Override
public TypeDescriptor toTypeDescriptor() { public TypeDescriptor toTypeDescriptor() {
return this.typeDescriptor; return this.pd.getTypeDescriptor();
} }
@Override @Override
public ResolvableType getResolvableType() { public ResolvableType getResolvableType() {
return this.typeDescriptor.getResolvableType(); return this.pd.getReadMethodType();
} }
@Override @Override
public TypeDescriptor getMapValueType(int nestingLevel) { public TypeDescriptor getMapValueType(int nestingLevel) {
return new TypeDescriptor( return new TypeDescriptor(
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asMap().getGeneric(1), this.pd.getReadMethodType().getNested(nestingLevel).asMap().getGeneric(1),
null, this.typeDescriptor.getAnnotations()); null, this.pd.getTypeDescriptor().getAnnotations());
} }
@Override @Override
public TypeDescriptor getCollectionType(int nestingLevel) { public TypeDescriptor getCollectionType(int nestingLevel) {
return new TypeDescriptor( return new TypeDescriptor(
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asCollection().getGeneric(), this.pd.getReadMethodType().getNested(nestingLevel).asCollection().getGeneric(),
null, this.typeDescriptor.getAnnotations()); null, this.pd.getTypeDescriptor().getAnnotations());
} }
@Override @Override
@Nullable @Nullable
public TypeDescriptor nested(int level) { public TypeDescriptor nested(int level) {
return TypeDescriptor.nested(property(this.pd), level); return this.pd.getTypeDescriptor().nested(level);
} }
@Override @Override
@Nullable @Nullable
public Object getValue() throws Exception { public Object getValue() throws Exception {
Method readMethod = this.pd.getReadMethod(); Method readMethod = this.pd.getReadMethod();
Assert.state(readMethod != null, "No read method available");
ReflectionUtils.makeAccessible(readMethod); ReflectionUtils.makeAccessible(readMethod);
return readMethod.invoke(getWrappedInstance(), (Object[]) null); return readMethod.invoke(getWrappedInstance(), (Object[]) null);
} }
@Override @Override
public void setValue(@Nullable Object value) throws Exception { public void setValue(@Nullable Object value) throws Exception {
Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd ? Method writeMethod = this.pd.getWriteMethodForActualAccess();
typeAwarePd.getWriteMethodForActualAccess() : this.pd.getWriteMethod());
ReflectionUtils.makeAccessible(writeMethod); ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(getWrappedInstance(), value); writeMethod.invoke(getWrappedInstance(), value);
} }

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

@ -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"); * 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.
@ -35,7 +35,6 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -235,9 +234,6 @@ public final class CachedIntrospectionResults {
/** PropertyDescriptor objects keyed by property name String. */ /** PropertyDescriptor objects keyed by property name String. */
private final Map<String, PropertyDescriptor> propertyDescriptors; 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. * Create a new CachedIntrospectionResults instance for the given class.
@ -300,8 +296,6 @@ public final class CachedIntrospectionResults {
// - accessor method directly referring to instance field of same name // - accessor method directly referring to instance field of same name
// - same convention for component accessors of Java 15 record classes // - same convention for component accessors of Java 15 record classes
introspectPlainAccessors(beanClass, readMethodNames); introspectPlainAccessors(beanClass, readMethodNames);
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
} }
catch (IntrospectionException ex) { catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex); throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
@ -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;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.BridgeMethodResolver; import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter; 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.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -57,6 +59,15 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
@Nullable @Nullable
private MethodParameter writeMethodParameter; private MethodParameter writeMethodParameter;
@Nullable
private volatile ResolvableType writeMethodType;
@Nullable
private ResolvableType readMethodType;
@Nullable
private volatile TypeDescriptor typeDescriptor;
@Nullable @Nullable
private Class<?> propertyType; private Class<?> propertyType;
@ -107,7 +118,8 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
} }
if (this.readMethod != null) { 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) { else if (this.writeMethodParameter != null) {
this.propertyType = this.writeMethodParameter.getParameterType(); this.propertyType = this.writeMethodParameter.getParameterType();
@ -150,6 +162,30 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
return this.writeMethodParameter; 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 @Override
@Nullable @Nullable
public Class<?> getPropertyType() { public Class<?> getPropertyType() {

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

@ -170,6 +170,33 @@ public class TypeDescriptor implements Serializable {
return this.resolvableType.getSource(); 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 * Narrows this {@link TypeDescriptor} by setting its type to the class of the
* provided value. * provided value.
@ -335,9 +362,9 @@ public class TypeDescriptor implements Serializable {
return new TypeDescriptor(getResolvableType().getComponentType(), null, getAnnotations()); return new TypeDescriptor(getResolvableType().getComponentType(), null, getAnnotations());
} }
if (Stream.class.isAssignableFrom(getType())) { 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 {
@Nullable @Nullable
public TypeDescriptor getMapKeyTypeDescriptor() { public TypeDescriptor getMapKeyTypeDescriptor() {
Assert.state(isMap(), "Not a [java.util.Map]"); 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 {
@Nullable @Nullable
public TypeDescriptor getMapValueTypeDescriptor() { public TypeDescriptor getMapValueTypeDescriptor() {
Assert.state(isMap(), "Not a [java.util.Map]"); 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 {
return narrow(mapValue, getMapValueTypeDescriptor()); return narrow(mapValue, getMapValueTypeDescriptor());
} }
@Nullable
private TypeDescriptor getRelatedIfResolvable(ResolvableType type) {
if (type.resolve() == null) {
return null;
}
return new TypeDescriptor(type, null, getAnnotations());
}
@Nullable @Nullable
private TypeDescriptor narrow(@Nullable Object value, @Nullable TypeDescriptor typeDescriptor) { private TypeDescriptor narrow(@Nullable Object value, @Nullable TypeDescriptor typeDescriptor) {
if (typeDescriptor != null) { if (typeDescriptor != null) {
@ -645,7 +680,7 @@ public class TypeDescriptor implements Serializable {
throw new IllegalArgumentException("MethodParameter nesting level must be 1: " + throw new IllegalArgumentException("MethodParameter nesting level must be 1: " +
"use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal"); "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 {
*/ */
@Nullable @Nullable
public static TypeDescriptor nested(Field field, int nestingLevel) { 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 {
*/ */
@Nullable @Nullable
public static TypeDescriptor nested(Property property, int nestingLevel) { public static TypeDescriptor nested(Property property, int nestingLevel) {
return nested(new TypeDescriptor(property), nestingLevel); return new TypeDescriptor(property).nested(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());
} }

Loading…
Cancel
Save