Additional caching for ResolvableTypes
Add additional caching to ResolvableTypes and SerializableTypeWrapper in order to improve SpEL performance. Issue: SPR-11388
This commit is contained in:
parent
319724f0b4
commit
7efd54e243
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<Type, Type> cache =
|
||||
new ConcurrentReferenceHashMap<Type, Type>(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());
|
||||
|
|
|
|||
|
|
@ -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<String, String> map = new HashMap<String, String>();
|
||||
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 {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue