Skip to content

Commit

Permalink
TypeDescriptor/ResolvableType cache in GenericTypeAwarePropertyDescri…
Browse files Browse the repository at this point in the history
…ptor

Closes gh-31490
  • Loading branch information
jhoeller committed Oct 24, 2023
1 parent 93b0b66 commit 83870e3
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ private static void copyProperties(Object source, Object target, @Nullable Class
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);
Expand All @@ -817,7 +817,9 @@ private static void copyProperties(Object source, Object target, @Nullable Class
}
}

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());
Expand All @@ -826,8 +828,8 @@ else if (paramType.equals(readMethod.getGenericReturnType())) {
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()) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
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;

/**
Expand Down Expand Up @@ -183,23 +183,15 @@ public Object convertForProperty(@Nullable Object value, String propertyName) th
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
Expand Down Expand Up @@ -234,58 +226,55 @@ public PropertyDescriptor getPropertyDescriptor(String propertyName) throws Inva

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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -35,7 +35,6 @@
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;
Expand Down Expand Up @@ -235,9 +234,6 @@ private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionExce
/** 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.
Expand Down Expand Up @@ -300,8 +296,6 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
// - 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);
Expand Down Expand Up @@ -410,14 +404,4 @@ private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> bean
}
}

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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -107,7 +118,8 @@ public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyNam
}

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();
Expand Down Expand Up @@ -150,6 +162,30 @@ public MethodParameter getWriteMethodParameter() {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,33 @@ public Object 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
* provided value.
Expand Down Expand Up @@ -335,9 +362,9 @@ public TypeDescriptor getElementTypeDescriptor() {
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));
}

/**
Expand Down Expand Up @@ -380,7 +407,7 @@ public boolean isMap() {
@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));
}

/**
Expand Down Expand Up @@ -417,7 +444,7 @@ public TypeDescriptor getMapKeyTypeDescriptor(Object mapKey) {
@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));
}

/**
Expand All @@ -442,6 +469,14 @@ public TypeDescriptor getMapValueTypeDescriptor(Object mapValue) {
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) {
Expand Down Expand Up @@ -645,7 +680,7 @@ public static TypeDescriptor nested(MethodParameter methodParameter, int nesting
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);
}

/**
Expand All @@ -671,7 +706,7 @@ public static TypeDescriptor nested(MethodParameter methodParameter, int nesting
*/
@Nullable
public static TypeDescriptor nested(Field field, int nestingLevel) {
return nested(new TypeDescriptor(field), nestingLevel);
return new TypeDescriptor(field).nested(nestingLevel);
}

/**
Expand All @@ -697,33 +732,7 @@ public static TypeDescriptor nested(Field field, int nestingLevel) {
*/
@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);
}


Expand Down

0 comments on commit 83870e3

Please sign in to comment.