Skip to content

Commit 83870e3

Browse files
committed
TypeDescriptor/ResolvableType cache in GenericTypeAwarePropertyDescriptor
Closes gh-31490
1 parent 93b0b66 commit 83870e3

File tree

5 files changed

+101
-81
lines changed

5 files changed

+101
-81
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ private static void copyProperties(Object source, Object target, @Nullable Class
799799
if (sourcePd != null) {
800800
Method readMethod = sourcePd.getReadMethod();
801801
if (readMethod != null) {
802-
if (isAssignable(writeMethod, readMethod)) {
802+
if (isAssignable(writeMethod, readMethod, sourcePd, targetPd)) {
803803
try {
804804
ReflectionUtils.makeAccessible(readMethod);
805805
Object value = readMethod.invoke(source);
@@ -817,7 +817,9 @@ private static void copyProperties(Object source, Object target, @Nullable Class
817817
}
818818
}
819819

820-
private static boolean isAssignable(Method writeMethod, Method readMethod) {
820+
private static boolean isAssignable(Method writeMethod, Method readMethod,
821+
PropertyDescriptor sourcePd, PropertyDescriptor targetPd) {
822+
821823
Type paramType = writeMethod.getGenericParameterTypes()[0];
822824
if (paramType instanceof Class<?> clazz) {
823825
return ClassUtils.isAssignable(clazz, readMethod.getReturnType());
@@ -826,8 +828,8 @@ else if (paramType.equals(readMethod.getGenericReturnType())) {
826828
return true;
827829
}
828830
else {
829-
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod);
830-
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0);
831+
ResolvableType sourceType = ((GenericTypeAwarePropertyDescriptor) sourcePd).getReadMethodType();
832+
ResolvableType targetType = ((GenericTypeAwarePropertyDescriptor) targetPd).getWriteMethodType();
831833
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
832834
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
833835
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :

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

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
import java.lang.reflect.Method;
2121

2222
import org.springframework.core.ResolvableType;
23-
import org.springframework.core.convert.Property;
2423
import org.springframework.core.convert.TypeDescriptor;
2524
import org.springframework.lang.Nullable;
25+
import org.springframework.util.Assert;
2626
import org.springframework.util.ReflectionUtils;
2727

2828
/**
@@ -183,23 +183,15 @@ public Object convertForProperty(@Nullable Object value, String propertyName) th
183183
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
184184
"No property '" + propertyName + "' found");
185185
}
186-
TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
187-
if (td == null) {
188-
td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
189-
}
186+
TypeDescriptor td = ((GenericTypeAwarePropertyDescriptor) pd).getTypeDescriptor();
190187
return convertForProperty(propertyName, null, value, td);
191188
}
192189

193-
private Property property(PropertyDescriptor pd) {
194-
GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
195-
return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
196-
}
197-
198190
@Override
199191
@Nullable
200192
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
201193
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
202-
return (pd != null ? new BeanPropertyHandler(pd) : null);
194+
return (pd != null ? new BeanPropertyHandler((GenericTypeAwarePropertyDescriptor) pd) : null);
203195
}
204196

205197
@Override
@@ -234,58 +226,55 @@ public PropertyDescriptor getPropertyDescriptor(String propertyName) throws Inva
234226

235227
private class BeanPropertyHandler extends PropertyHandler {
236228

237-
private final PropertyDescriptor pd;
238-
239-
private final TypeDescriptor typeDescriptor;
229+
private final GenericTypeAwarePropertyDescriptor pd;
240230

241-
public BeanPropertyHandler(PropertyDescriptor pd) {
231+
public BeanPropertyHandler(GenericTypeAwarePropertyDescriptor pd) {
242232
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
243233
this.pd = pd;
244-
this.typeDescriptor = new TypeDescriptor(property(pd));
245234
}
246235

247236
@Override
248237
public TypeDescriptor toTypeDescriptor() {
249-
return this.typeDescriptor;
238+
return this.pd.getTypeDescriptor();
250239
}
251240

252241
@Override
253242
public ResolvableType getResolvableType() {
254-
return this.typeDescriptor.getResolvableType();
243+
return this.pd.getReadMethodType();
255244
}
256245

257246
@Override
258247
public TypeDescriptor getMapValueType(int nestingLevel) {
259248
return new TypeDescriptor(
260-
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asMap().getGeneric(1),
261-
null, this.typeDescriptor.getAnnotations());
249+
this.pd.getReadMethodType().getNested(nestingLevel).asMap().getGeneric(1),
250+
null, this.pd.getTypeDescriptor().getAnnotations());
262251
}
263252

264253
@Override
265254
public TypeDescriptor getCollectionType(int nestingLevel) {
266255
return new TypeDescriptor(
267-
this.typeDescriptor.getResolvableType().getNested(nestingLevel).asCollection().getGeneric(),
268-
null, this.typeDescriptor.getAnnotations());
256+
this.pd.getReadMethodType().getNested(nestingLevel).asCollection().getGeneric(),
257+
null, this.pd.getTypeDescriptor().getAnnotations());
269258
}
270259

271260
@Override
272261
@Nullable
273262
public TypeDescriptor nested(int level) {
274-
return TypeDescriptor.nested(property(this.pd), level);
263+
return this.pd.getTypeDescriptor().nested(level);
275264
}
276265

277266
@Override
278267
@Nullable
279268
public Object getValue() throws Exception {
280269
Method readMethod = this.pd.getReadMethod();
270+
Assert.state(readMethod != null, "No read method available");
281271
ReflectionUtils.makeAccessible(readMethod);
282272
return readMethod.invoke(getWrappedInstance(), (Object[]) null);
283273
}
284274

285275
@Override
286276
public void setValue(@Nullable Object value) throws Exception {
287-
Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd ?
288-
typeAwarePd.getWriteMethodForActualAccess() : this.pd.getWriteMethod());
277+
Method writeMethod = this.pd.getWriteMethodForActualAccess();
289278
ReflectionUtils.makeAccessible(writeMethod);
290279
writeMethod.invoke(getWrappedInstance(), value);
291280
}

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

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@
3535
import org.apache.commons.logging.Log;
3636
import org.apache.commons.logging.LogFactory;
3737

38-
import org.springframework.core.convert.TypeDescriptor;
3938
import org.springframework.core.io.support.SpringFactoriesLoader;
4039
import org.springframework.lang.Nullable;
4140
import org.springframework.util.ClassUtils;
@@ -235,9 +234,6 @@ private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionExce
235234
/** PropertyDescriptor objects keyed by property name String. */
236235
private final Map<String, PropertyDescriptor> propertyDescriptors;
237236

238-
/** TypeDescriptor objects keyed by PropertyDescriptor. */
239-
private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;
240-
241237

242238
/**
243239
* Create a new CachedIntrospectionResults instance for the given class.
@@ -300,8 +296,6 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
300296
// - accessor method directly referring to instance field of same name
301297
// - same convention for component accessors of Java 15 record classes
302298
introspectPlainAccessors(beanClass, readMethodNames);
303-
304-
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
305299
}
306300
catch (IntrospectionException ex) {
307301
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
@@ -410,14 +404,4 @@ private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> bean
410404
}
411405
}
412406

413-
TypeDescriptor addTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) {
414-
TypeDescriptor existing = this.typeDescriptorCache.putIfAbsent(pd, td);
415-
return (existing != null ? existing : td);
416-
}
417-
418-
@Nullable
419-
TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) {
420-
return this.typeDescriptorCache.get(pd);
421-
}
422-
423407
}

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import org.apache.commons.logging.LogFactory;
2727

2828
import org.springframework.core.BridgeMethodResolver;
29-
import org.springframework.core.GenericTypeResolver;
3029
import org.springframework.core.MethodParameter;
30+
import org.springframework.core.ResolvableType;
31+
import org.springframework.core.convert.Property;
32+
import org.springframework.core.convert.TypeDescriptor;
3133
import org.springframework.lang.Nullable;
3234
import org.springframework.util.Assert;
3335
import org.springframework.util.ClassUtils;
@@ -57,6 +59,15 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
5759
@Nullable
5860
private MethodParameter writeMethodParameter;
5961

62+
@Nullable
63+
private volatile ResolvableType writeMethodType;
64+
65+
@Nullable
66+
private ResolvableType readMethodType;
67+
68+
@Nullable
69+
private volatile TypeDescriptor typeDescriptor;
70+
6071
@Nullable
6172
private Class<?> propertyType;
6273

@@ -107,7 +118,8 @@ public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyNam
107118
}
108119

109120
if (this.readMethod != null) {
110-
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
121+
this.readMethodType = ResolvableType.forMethodReturnType(this.readMethod, this.beanClass);
122+
this.propertyType = this.readMethodType.resolve(this.readMethod.getReturnType());
111123
}
112124
else if (this.writeMethodParameter != null) {
113125
this.propertyType = this.writeMethodParameter.getParameterType();
@@ -150,6 +162,30 @@ public MethodParameter getWriteMethodParameter() {
150162
return this.writeMethodParameter;
151163
}
152164

165+
public ResolvableType getWriteMethodType() {
166+
ResolvableType writeMethodType = this.writeMethodType;
167+
if (writeMethodType == null) {
168+
writeMethodType = ResolvableType.forMethodParameter(getWriteMethodParameter());
169+
this.writeMethodType = writeMethodType;
170+
}
171+
return writeMethodType;
172+
}
173+
174+
public ResolvableType getReadMethodType() {
175+
Assert.state(this.readMethodType != null, "No read method available");
176+
return this.readMethodType;
177+
}
178+
179+
public TypeDescriptor getTypeDescriptor() {
180+
TypeDescriptor typeDescriptor = this.typeDescriptor;
181+
if (typeDescriptor == null) {
182+
Property property = new Property(getBeanClass(), getReadMethod(), getWriteMethod(), getName());
183+
typeDescriptor = new TypeDescriptor(property);
184+
this.typeDescriptor = typeDescriptor;
185+
}
186+
return typeDescriptor;
187+
}
188+
153189
@Override
154190
@Nullable
155191
public Class<?> getPropertyType() {

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

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,33 @@ public Object getSource() {
170170
return this.resolvableType.getSource();
171171
}
172172

173+
174+
/**
175+
* Create a type descriptor for a nested type declared within this descriptor.
176+
* @param nestingLevel the nesting level of the collection/array element or
177+
* map key/value declaration within the property
178+
* @return the nested type descriptor at the specified nesting level, or
179+
* {@code null} if it could not be obtained
180+
* @since 6.1
181+
*/
182+
@Nullable
183+
public TypeDescriptor nested(int nestingLevel) {
184+
ResolvableType nested = this.resolvableType;
185+
for (int i = 0; i < nestingLevel; i++) {
186+
if (Object.class == nested.getType()) {
187+
// Could be a collection type but we don't know about its element type,
188+
// so let's just assume there is an element type of type Object...
189+
}
190+
else {
191+
nested = nested.getNested(2);
192+
}
193+
}
194+
if (nested == ResolvableType.NONE) {
195+
return null;
196+
}
197+
return getRelatedIfResolvable(nested);
198+
}
199+
173200
/**
174201
* Narrows this {@link TypeDescriptor} by setting its type to the class of the
175202
* provided value.
@@ -335,9 +362,9 @@ public TypeDescriptor getElementTypeDescriptor() {
335362
return new TypeDescriptor(getResolvableType().getComponentType(), null, getAnnotations());
336363
}
337364
if (Stream.class.isAssignableFrom(getType())) {
338-
return getRelatedIfResolvable(this, getResolvableType().as(Stream.class).getGeneric(0));
365+
return getRelatedIfResolvable(getResolvableType().as(Stream.class).getGeneric(0));
339366
}
340-
return getRelatedIfResolvable(this, getResolvableType().asCollection().getGeneric(0));
367+
return getRelatedIfResolvable(getResolvableType().asCollection().getGeneric(0));
341368
}
342369

343370
/**
@@ -380,7 +407,7 @@ public boolean isMap() {
380407
@Nullable
381408
public TypeDescriptor getMapKeyTypeDescriptor() {
382409
Assert.state(isMap(), "Not a [java.util.Map]");
383-
return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(0));
410+
return getRelatedIfResolvable(getResolvableType().asMap().getGeneric(0));
384411
}
385412

386413
/**
@@ -417,7 +444,7 @@ public TypeDescriptor getMapKeyTypeDescriptor(Object mapKey) {
417444
@Nullable
418445
public TypeDescriptor getMapValueTypeDescriptor() {
419446
Assert.state(isMap(), "Not a [java.util.Map]");
420-
return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(1));
447+
return getRelatedIfResolvable(getResolvableType().asMap().getGeneric(1));
421448
}
422449

423450
/**
@@ -442,6 +469,14 @@ public TypeDescriptor getMapValueTypeDescriptor(Object mapValue) {
442469
return narrow(mapValue, getMapValueTypeDescriptor());
443470
}
444471

472+
@Nullable
473+
private TypeDescriptor getRelatedIfResolvable(ResolvableType type) {
474+
if (type.resolve() == null) {
475+
return null;
476+
}
477+
return new TypeDescriptor(type, null, getAnnotations());
478+
}
479+
445480
@Nullable
446481
private TypeDescriptor narrow(@Nullable Object value, @Nullable TypeDescriptor typeDescriptor) {
447482
if (typeDescriptor != null) {
@@ -645,7 +680,7 @@ public static TypeDescriptor nested(MethodParameter methodParameter, int nesting
645680
throw new IllegalArgumentException("MethodParameter nesting level must be 1: " +
646681
"use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal");
647682
}
648-
return nested(new TypeDescriptor(methodParameter), nestingLevel);
683+
return new TypeDescriptor(methodParameter).nested(nestingLevel);
649684
}
650685

651686
/**
@@ -671,7 +706,7 @@ public static TypeDescriptor nested(MethodParameter methodParameter, int nesting
671706
*/
672707
@Nullable
673708
public static TypeDescriptor nested(Field field, int nestingLevel) {
674-
return nested(new TypeDescriptor(field), nestingLevel);
709+
return new TypeDescriptor(field).nested(nestingLevel);
675710
}
676711

677712
/**
@@ -697,33 +732,7 @@ public static TypeDescriptor nested(Field field, int nestingLevel) {
697732
*/
698733
@Nullable
699734
public static TypeDescriptor nested(Property property, int nestingLevel) {
700-
return nested(new TypeDescriptor(property), nestingLevel);
701-
}
702-
703-
@Nullable
704-
private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
705-
ResolvableType nested = typeDescriptor.resolvableType;
706-
for (int i = 0; i < nestingLevel; i++) {
707-
if (Object.class == nested.getType()) {
708-
// Could be a collection type but we don't know about its element type,
709-
// so let's just assume there is an element type of type Object...
710-
}
711-
else {
712-
nested = nested.getNested(2);
713-
}
714-
}
715-
if (nested == ResolvableType.NONE) {
716-
return null;
717-
}
718-
return getRelatedIfResolvable(typeDescriptor, nested);
719-
}
720-
721-
@Nullable
722-
private static TypeDescriptor getRelatedIfResolvable(TypeDescriptor source, ResolvableType type) {
723-
if (type.resolve() == null) {
724-
return null;
725-
}
726-
return new TypeDescriptor(type, null, source.getAnnotations());
735+
return new TypeDescriptor(property).nested(nestingLevel);
727736
}
728737

729738

0 commit comments

Comments
 (0)