From 7efd54e243ab0451eea3f1cded7cfd7b512aa563 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 14 Feb 2014 16:11:08 -0800 Subject: [PATCH] Additional caching for ResolvableTypes Add additional caching to ResolvableTypes and SerializableTypeWrapper in order to improve SpEL performance. Issue: SPR-11388 --- .../springframework/core/ResolvableType.java | 45 +++++++++++++------ .../core/SerializableTypeWrapper.java | 12 ++++- .../expression/spel/MapAccessTests.java | 25 ++++++++++- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 6208bb0e765..7a1dd1495ad 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -115,6 +115,12 @@ public final class ResolvableType implements Serializable { */ private final Class resolved; + private ResolvableType superType; + + private ResolvableType[] interfaces; + + private ResolvableType[] generics; + /** * Private constructor used to create a new {@link ResolvableType} for resolution purposes. @@ -360,7 +366,11 @@ public final class ResolvableType implements Serializable { if (resolved == null || resolved.getGenericSuperclass() == null) { return NONE; } - return forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver()); + if (this.superType == null) { + this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), + asVariableResolver()); + } + return this.superType; } /** @@ -374,7 +384,11 @@ public final class ResolvableType implements Serializable { if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) { return EMPTY_TYPES_ARRAY; } - return forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver()); + if (this.interfaces == null) { + this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), + asVariableResolver()); + } + return this.interfaces; } /** @@ -551,19 +565,24 @@ public final class ResolvableType implements Serializable { if (this == NONE) { return EMPTY_TYPES_ARRAY; } - if (this.type instanceof Class) { - Class typeClass = (Class) this.type; - return forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver); - } - if (this.type instanceof ParameterizedType) { - Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments(); - ResolvableType[] generics = new ResolvableType[actualTypeArguments.length]; - for (int i = 0; i < actualTypeArguments.length; i++) { - generics[i] = forType(actualTypeArguments[i], this.variableResolver); + if (this.generics == null) { + if (this.type instanceof Class) { + Class typeClass = (Class) this.type; + this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver); + } + else if (this.type instanceof ParameterizedType) { + Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments(); + ResolvableType[] generics = new ResolvableType[actualTypeArguments.length]; + for (int i = 0; i < actualTypeArguments.length; i++) { + generics[i] = forType(actualTypeArguments[i], this.variableResolver); + } + this.generics = generics; + } + else { + this.generics = resolveType().getGenerics(); } - return generics; } - return resolveType().getGenerics(); + return this.generics; } /** diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java index 7250ada7156..9697aa81732 100644 --- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java +++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java @@ -31,6 +31,7 @@ import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; /** @@ -63,6 +64,9 @@ abstract class SerializableTypeWrapper { private static final Method GET_TYPE_PROVIDER_METHOD = ReflectionUtils.findMethod( SerializableTypeProxy.class, "getTypeProvider"); + private static final ConcurrentReferenceHashMap cache = + new ConcurrentReferenceHashMap(256); + /** * Return a {@link Serializable} variant of {@link Field#getGenericType()}. */ @@ -150,13 +154,19 @@ abstract class SerializableTypeWrapper { if (provider.getType() instanceof Serializable || provider.getType() == null) { return provider.getType(); } + Type cached = cache.get(provider.getType()); + if(cached != null) { + return cached; + } for (Class type : SUPPORTED_SERIALIZABLE_TYPES) { if (type.isAssignableFrom(provider.getType().getClass())) { ClassLoader classLoader = provider.getClass().getClassLoader(); Class[] interfaces = new Class[] { type, SerializableTypeProxy.class, Serializable.class }; InvocationHandler handler = new TypeProxyInvocationHandler(provider); - return (Type) Proxy.newProxyInstance(classLoader, interfaces, handler); + cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler); + cache.put(provider.getType(), cached); + return cached; } } throw new IllegalArgumentException("Unsupported Type class " + provider.getType().getClass().getName()); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java index 5cec0e95034..ae65b1983da 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.Map; import org.junit.Test; - import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -29,6 +28,11 @@ import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; +import org.springframework.util.StopWatch; + +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @@ -96,6 +100,25 @@ public class MapAccessTests extends AbstractExpressionTests { assertEquals("value", expr.getValue(map)); } + @Test + public void testGetValuePerformance() throws Exception { + Assume.group(TestGroup.PERFORMANCE); + Map map = new HashMap(); + map.put("key", "value"); + EvaluationContext context = new StandardEvaluationContext(map); + + ExpressionParser spelExpressionParser = new SpelExpressionParser(); + Expression expr = spelExpressionParser.parseExpression("#root['key']"); + + StopWatch s = new StopWatch(); + s.start(); + for (int i = 0; i < 10000; i++) { + expr.getValue(context); + } + s.stop(); + assertThat(s.getTotalTimeMillis(), lessThan(200L)); + } + public static class TestBean {