Skip to content

Commit 7efd54e

Browse files
author
Phillip Webb
committed
Additional caching for ResolvableTypes
Add additional caching to ResolvableTypes and SerializableTypeWrapper in order to improve SpEL performance. Issue: SPR-11388
1 parent 319724f commit 7efd54e

File tree

3 files changed

+67
-15
lines changed

3 files changed

+67
-15
lines changed

spring-core/src/main/java/org/springframework/core/ResolvableType.java

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ public final class ResolvableType implements Serializable {
115115
*/
116116
private final Class<?> resolved;
117117

118+
private ResolvableType superType;
119+
120+
private ResolvableType[] interfaces;
121+
122+
private ResolvableType[] generics;
123+
118124

119125
/**
120126
* Private constructor used to create a new {@link ResolvableType} for resolution purposes.
@@ -360,7 +366,11 @@ public ResolvableType getSuperType() {
360366
if (resolved == null || resolved.getGenericSuperclass() == null) {
361367
return NONE;
362368
}
363-
return forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver());
369+
if (this.superType == null) {
370+
this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved),
371+
asVariableResolver());
372+
}
373+
return this.superType;
364374
}
365375

366376
/**
@@ -374,7 +384,11 @@ public ResolvableType[] getInterfaces() {
374384
if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) {
375385
return EMPTY_TYPES_ARRAY;
376386
}
377-
return forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver());
387+
if (this.interfaces == null) {
388+
this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved),
389+
asVariableResolver());
390+
}
391+
return this.interfaces;
378392
}
379393

380394
/**
@@ -551,19 +565,24 @@ public ResolvableType[] getGenerics() {
551565
if (this == NONE) {
552566
return EMPTY_TYPES_ARRAY;
553567
}
554-
if (this.type instanceof Class<?>) {
555-
Class<?> typeClass = (Class<?>) this.type;
556-
return forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
557-
}
558-
if (this.type instanceof ParameterizedType) {
559-
Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
560-
ResolvableType[] generics = new ResolvableType[actualTypeArguments.length];
561-
for (int i = 0; i < actualTypeArguments.length; i++) {
562-
generics[i] = forType(actualTypeArguments[i], this.variableResolver);
568+
if (this.generics == null) {
569+
if (this.type instanceof Class<?>) {
570+
Class<?> typeClass = (Class<?>) this.type;
571+
this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
572+
}
573+
else if (this.type instanceof ParameterizedType) {
574+
Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
575+
ResolvableType[] generics = new ResolvableType[actualTypeArguments.length];
576+
for (int i = 0; i < actualTypeArguments.length; i++) {
577+
generics[i] = forType(actualTypeArguments[i], this.variableResolver);
578+
}
579+
this.generics = generics;
580+
}
581+
else {
582+
this.generics = resolveType().getGenerics();
563583
}
564-
return generics;
565584
}
566-
return resolveType().getGenerics();
585+
return this.generics;
567586
}
568587

569588
/**

spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.lang.reflect.WildcardType;
3232

3333
import org.springframework.util.Assert;
34+
import org.springframework.util.ConcurrentReferenceHashMap;
3435
import org.springframework.util.ReflectionUtils;
3536

3637
/**
@@ -63,6 +64,9 @@ abstract class SerializableTypeWrapper {
6364
private static final Method GET_TYPE_PROVIDER_METHOD = ReflectionUtils.findMethod(
6465
SerializableTypeProxy.class, "getTypeProvider");
6566

67+
private static final ConcurrentReferenceHashMap<Type, Type> cache =
68+
new ConcurrentReferenceHashMap<Type, Type>(256);
69+
6670
/**
6771
* Return a {@link Serializable} variant of {@link Field#getGenericType()}.
6872
*/
@@ -150,13 +154,19 @@ static Type forTypeProvider(final TypeProvider provider) {
150154
if (provider.getType() instanceof Serializable || provider.getType() == null) {
151155
return provider.getType();
152156
}
157+
Type cached = cache.get(provider.getType());
158+
if(cached != null) {
159+
return cached;
160+
}
153161
for (Class<?> type : SUPPORTED_SERIALIZABLE_TYPES) {
154162
if (type.isAssignableFrom(provider.getType().getClass())) {
155163
ClassLoader classLoader = provider.getClass().getClassLoader();
156164
Class<?>[] interfaces = new Class<?>[] { type,
157165
SerializableTypeProxy.class, Serializable.class };
158166
InvocationHandler handler = new TypeProxyInvocationHandler(provider);
159-
return (Type) Proxy.newProxyInstance(classLoader, interfaces, handler);
167+
cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler);
168+
cache.put(provider.getType(), cached);
169+
return cached;
160170
}
161171
}
162172
throw new IllegalArgumentException("Unsupported Type class " + provider.getType().getClass().getName());

spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Map;
2121

2222
import org.junit.Test;
23-
2423
import org.springframework.expression.AccessException;
2524
import org.springframework.expression.EvaluationContext;
2625
import org.springframework.expression.Expression;
@@ -29,6 +28,11 @@
2928
import org.springframework.expression.TypedValue;
3029
import org.springframework.expression.spel.standard.SpelExpressionParser;
3130
import org.springframework.expression.spel.support.StandardEvaluationContext;
31+
import org.springframework.tests.Assume;
32+
import org.springframework.tests.TestGroup;
33+
import org.springframework.util.StopWatch;
34+
35+
import static org.hamcrest.Matchers.*;
3236

3337
import static org.junit.Assert.*;
3438

@@ -96,6 +100,25 @@ public void testGetValueFromRootMap() {
96100
assertEquals("value", expr.getValue(map));
97101
}
98102

103+
@Test
104+
public void testGetValuePerformance() throws Exception {
105+
Assume.group(TestGroup.PERFORMANCE);
106+
Map<String, String> map = new HashMap<String, String>();
107+
map.put("key", "value");
108+
EvaluationContext context = new StandardEvaluationContext(map);
109+
110+
ExpressionParser spelExpressionParser = new SpelExpressionParser();
111+
Expression expr = spelExpressionParser.parseExpression("#root['key']");
112+
113+
StopWatch s = new StopWatch();
114+
s.start();
115+
for (int i = 0; i < 10000; i++) {
116+
expr.getValue(context);
117+
}
118+
s.stop();
119+
assertThat(s.getTotalTimeMillis(), lessThan(200L));
120+
}
121+
99122

100123
public static class TestBean {
101124

0 commit comments

Comments
 (0)