From c7b324b89b6d0d0f000f0619f22fcfca21cf03c6 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sat, 24 Jan 2015 15:30:22 +0100 Subject: [PATCH] Move cached expression evaluation abstraction Move MethodCacheKey and related classes to the expression package so that other parts of the framework can benefit ot it. CacheExpressionEvaluator is a base class that can be used to cache SpEL expressions based on its annotation source (i.e. method). Sub-classing that base class provides a simple to use API to retrieve Expression instances efficiently. Issue: SPR-12622 --- ...AbstractFallbackJCacheOperationSource.java | 6 +- .../AbstractFallbackCacheOperationSource.java | 5 +- .../cache/interceptor/CacheAspectSupport.java | 11 +- .../interceptor/CacheEvaluationContext.java | 9 +- .../interceptor/ExpressionEvaluator.java | 72 ++--------- .../expression/AnnotatedElementKey.java} | 38 +++--- .../expression/CachedExpressionEvaluator.java | 112 ++++++++++++++++++ .../interceptor/ExpressionEvaluatorTests.java | 5 +- .../expression/AnnotatedElementKeyTests.java} | 22 ++-- .../CachedExpressionEvaluatorTests.java | 89 ++++++++++++++ 10 files changed, 265 insertions(+), 104 deletions(-) rename spring-context/src/main/java/org/springframework/{cache/interceptor/MethodCacheKey.java => context/expression/AnnotatedElementKey.java} (52%) create mode 100644 spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java rename spring-context/src/test/java/org/springframework/{cache/interceptor/MethodCacheKeyTests.java => context/expression/AnnotatedElementKeyTests.java} (68%) create mode 100644 spring-context/src/test/java/org/springframework/context/expression/CachedExpressionEvaluatorTests.java diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java index b6e2c0c7c1b..88813c3cda2 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cache.interceptor.MethodCacheKey; +import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.core.BridgeMethodResolver; import org.springframework.util.ClassUtils; @@ -57,7 +57,7 @@ public abstract class AbstractFallbackJCacheOperationSource @Override public JCacheOperation getCacheOperation(Method method, Class targetClass) { // First, see if we have a cached value. - Object cacheKey = new MethodCacheKey(method, targetClass); + Object cacheKey = new AnnotatedElementKey(method, targetClass); Object cached = this.cache.get(cacheKey); if (cached != null) { if (cached == NULL_CACHING_ATTRIBUTE) { diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java index 222ee095914..7308d562762 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.core.BridgeMethodResolver; import org.springframework.util.ClassUtils; @@ -121,7 +122,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera * @return the cache key (never {@code null}) */ protected Object getCacheKey(Method method, Class targetClass) { - return new MethodCacheKey(method, targetClass); + return new AnnotatedElementKey(method, targetClass); } private Collection computeCacheOperations(Method method, Class targetClass) { diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 779dfbbcde9..2c241f891d9 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -39,6 +39,7 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.expression.EvaluationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -546,14 +547,14 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private final Collection caches; - private final MethodCacheKey methodCacheKey; + private final AnnotatedElementKey methodCacheKey; public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) { this.metadata = metadata; this.args = extractArgs(metadata.method, args); this.target = target; this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver); - this.methodCacheKey = new MethodCacheKey(metadata.method, metadata.targetClass); + this.methodCacheKey = new AnnotatedElementKey(metadata.method, metadata.targetClass); } @Override @@ -659,11 +660,11 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private final CacheOperation cacheOperation; - private final MethodCacheKey methodCacheKey; + private final AnnotatedElementKey methodCacheKey; private CacheOperationCacheKey(CacheOperation cacheOperation, Method method, Class targetClass) { this.cacheOperation = cacheOperation; - this.methodCacheKey = new MethodCacheKey(method, targetClass); + this.methodCacheKey = new AnnotatedElementKey(method, targetClass); } @Override diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java index 492e8a6ead6..1720924b96e 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import org.springframework.aop.support.AopUtils; +import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.ObjectUtils; @@ -53,7 +54,7 @@ class CacheEvaluationContext extends StandardEvaluationContext { private final Class targetClass; - private final Map methodCache; + private final Map methodCache; private final List unavailableVariables; @@ -61,7 +62,7 @@ class CacheEvaluationContext extends StandardEvaluationContext { CacheEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method, - Object[] args, Class targetClass, Map methodCache) { + Object[] args, Class targetClass, Map methodCache) { super(rootObject); this.paramDiscoverer = paramDiscoverer; @@ -110,7 +111,7 @@ class CacheEvaluationContext extends StandardEvaluationContext { return; } - MethodCacheKey methodKey = new MethodCacheKey(this.method, this.targetClass); + AnnotatedElementKey methodKey = new AnnotatedElementKey(this.method, this.targetClass); Method targetMethod = this.methodCache.get(methodKey); if (targetMethod == null) { targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass); diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java index 273e926c5ba..b7a75658ef6 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -22,19 +22,19 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.cache.Cache; +import org.springframework.context.expression.AnnotatedElementKey; +import org.springframework.context.expression.CachedExpressionEvaluator; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.ObjectUtils; /** * Utility class handling the SpEL expression parsing. * Meant to be used as a reusable, thread-safe component. * *

Performs internal caching for performance reasons - * using {@link MethodCacheKey}. + * using {@link AnnotatedElementKey}. * * @author Costin Leau * @author Phillip Webb @@ -42,7 +42,7 @@ import org.springframework.util.ObjectUtils; * @author Stephane Nicoll * @since 3.1 */ -class ExpressionEvaluator { +class ExpressionEvaluator extends CachedExpressionEvaluator { /** * Indicate that there is no result variable. @@ -59,9 +59,6 @@ class ExpressionEvaluator { */ public static final String RESULT_VARIABLE = "result"; - - private final SpelExpressionParser parser = new SpelExpressionParser(); - // shared param discoverer since it caches data internally private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer(); @@ -71,7 +68,8 @@ class ExpressionEvaluator { private final Map unlessCache = new ConcurrentHashMap(64); - private final Map targetMethodCache = new ConcurrentHashMap(64); + private final Map targetMethodCache = + new ConcurrentHashMap(64); /** @@ -111,61 +109,17 @@ class ExpressionEvaluator { return evaluationContext; } - public Object key(String keyExpression, MethodCacheKey methodKey, EvaluationContext evalContext) { - return getExpression(this.keyCache, keyExpression, methodKey).getValue(evalContext); + public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) { + return getExpression(this.keyCache, methodKey, keyExpression).getValue(evalContext); } - public boolean condition(String conditionExpression, MethodCacheKey methodKey, EvaluationContext evalContext) { - return getExpression(this.conditionCache, conditionExpression, methodKey).getValue(evalContext, boolean.class); + public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) { + return getExpression(this.conditionCache, methodKey, conditionExpression).getValue(evalContext, boolean.class); } - public boolean unless(String unlessExpression, MethodCacheKey methodKey, EvaluationContext evalContext) { - return getExpression(this.unlessCache, unlessExpression, methodKey).getValue(evalContext, boolean.class); - } - - private Expression getExpression(Map cache, String expression, MethodCacheKey methodKey) { - ExpressionKey key = createKey(methodKey, expression); - Expression expr = cache.get(key); - if (expr == null) { - expr = this.parser.parseExpression(expression); - cache.put(key, expr); - } - return expr; + public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) { + return getExpression(this.unlessCache, methodKey, unlessExpression).getValue(evalContext, boolean.class); } - private ExpressionKey createKey(MethodCacheKey methodCacheKey, String expression) { - return new ExpressionKey(methodCacheKey, expression); - } - - - private static class ExpressionKey { - - private final MethodCacheKey methodCacheKey; - - private final String expression; - - public ExpressionKey(MethodCacheKey methodCacheKey, String expression) { - this.methodCacheKey = methodCacheKey; - this.expression = expression; - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (!(other instanceof ExpressionKey)) { - return false; - } - ExpressionKey otherKey = (ExpressionKey) other; - return (this.methodCacheKey.equals(otherKey.methodCacheKey) && - ObjectUtils.nullSafeEquals(this.expression, otherKey.expression)); - } - - @Override - public int hashCode() { - return this.methodCacheKey.hashCode() * 29 + (this.expression != null ? this.expression.hashCode() : 0); - } - } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java b/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java similarity index 52% rename from spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java rename to spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java index a74f3a20389..97fc7a5dc17 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java +++ b/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -14,32 +14,35 @@ * limitations under the License. */ -package org.springframework.cache.interceptor; +package org.springframework.context.expression; -import java.lang.reflect.Method; +import java.lang.reflect.AnnotatedElement; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * Represent a method on a particular {@link Class} and is suitable as a key. - * - *

Mainly for internal use within the framework. + * Represent an {@link AnnotatedElement} on a particular {@link Class} + * and is suitable as a key. * * @author Costin Leau * @author Stephane Nicoll - * @since 4.0.4 + * @since 4.2.0 + * @see CachedExpressionEvaluator */ -public final class MethodCacheKey { +public final class AnnotatedElementKey { - private final Method method; + private final AnnotatedElement element; private final Class targetClass; - - public MethodCacheKey(Method method, Class targetClass) { - Assert.notNull(method, "method must be set."); - this.method = method; + /** + * Create a new instance with the specified {@link AnnotatedElement} and + * optional target {@link Class}. + */ + public AnnotatedElementKey(AnnotatedElement element, Class targetClass) { + Assert.notNull(element, "AnnotatedElement must be set."); + this.element = element; this.targetClass = targetClass; } @@ -49,18 +52,17 @@ public final class MethodCacheKey { if (this == other) { return true; } - if (!(other instanceof MethodCacheKey)) { + if (!(other instanceof AnnotatedElementKey)) { return false; } - MethodCacheKey otherKey = (MethodCacheKey) other; - return (this.method.equals(otherKey.method) && + AnnotatedElementKey otherKey = (AnnotatedElementKey) other; + return (this.element.equals(otherKey.element) && ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); } - @Override public int hashCode() { - return this.method.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0); + return this.element.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0); } } diff --git a/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java new file mode 100644 index 00000000000..81d0c55dc8a --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2015 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.expression; + +import java.util.Map; + +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Shared utility class used to evaluate and cache SpEL expressions that + * are defined on {@link java.lang.reflect.AnnotatedElement}. + * + * @author Stephane Nicoll + * @since 4.2.0 + * @see AnnotatedElementKey + */ +public abstract class CachedExpressionEvaluator { + + private final SpelExpressionParser parser; + + /** + * Create a new instance with the specified {@link SpelExpressionParser}. + */ + protected CachedExpressionEvaluator(SpelExpressionParser parser) { + Assert.notNull(parser, "Parser must not be null"); + this.parser = parser; + } + + /** + * Create a new instance with a default {@link SpelExpressionParser}. + */ + protected CachedExpressionEvaluator() { + this(new SpelExpressionParser()); + } + + /** + * Return the {@link SpelExpressionParser} to use. + */ + protected SpelExpressionParser getParser() { + return this.parser; + } + + /** + * Return the {@link Expression} for the specified SpEL value + *

Parse the expression if it hasn't been already. + * @param cache the cache to use + * @param elementKey the element on which the expression is defined + * @param expression the expression to parse + */ + protected Expression getExpression(Map cache, + AnnotatedElementKey elementKey, String expression) { + ExpressionKey expressionKey = createKey(elementKey, expression); + Expression expr = cache.get(expressionKey); + if (expr == null) { + expr = getParser().parseExpression(expression); + cache.put(expressionKey, expr); + } + return expr; + } + + private ExpressionKey createKey(AnnotatedElementKey elementKey, String expression) { + return new ExpressionKey(elementKey, expression); + } + + protected static class ExpressionKey { + + private final AnnotatedElementKey key; + + private final String expression; + + protected ExpressionKey(AnnotatedElementKey key, String expression) { + this.key = key; + this.expression = expression; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ExpressionKey)) { + return false; + } + ExpressionKey otherKey = (ExpressionKey) other; + return (this.key.equals(otherKey.key) && + ObjectUtils.nullSafeEquals(this.expression, otherKey.expression)); + } + + @Override + public int hashCode() { + return this.key.hashCode() * 29 + (this.expression != null ? this.expression.hashCode() : 0); + } + } + +} diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java index e35f9c9cc8d..508a21fd8bc 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -27,6 +27,7 @@ import org.springframework.cache.annotation.AnnotationCacheOperationSource; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.ReflectionUtils; @@ -79,7 +80,7 @@ public class ExpressionEvaluatorTests { Iterator it = ops.iterator(); - MethodCacheKey key = new MethodCacheKey(method, AnnotatedClass.class); + AnnotatedElementKey key = new AnnotatedElementKey(method, AnnotatedClass.class); Object keyA = eval.key(it.next().getKey(), key, evalCtx); Object keyB = eval.key(it.next().getKey(), key, evalCtx); diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/MethodCacheKeyTests.java b/spring-context/src/test/java/org/springframework/context/expression/AnnotatedElementKeyTests.java similarity index 68% rename from spring-context/src/test/java/org/springframework/cache/interceptor/MethodCacheKeyTests.java rename to spring-context/src/test/java/org/springframework/context/expression/AnnotatedElementKeyTests.java index c25635f82df..8718c8159f8 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/MethodCacheKeyTests.java +++ b/spring-context/src/test/java/org/springframework/context/expression/AnnotatedElementKeyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cache.interceptor; +package org.springframework.context.expression; import java.lang.reflect.Method; @@ -29,7 +29,7 @@ import static org.junit.Assert.*; /** * @author Stephane Nicoll */ -public class MethodCacheKeyTests { +public class AnnotatedElementKeyTests { @Rule public final TestName name = new TestName(); @@ -37,15 +37,15 @@ public class MethodCacheKeyTests { @Test public void sameInstanceEquals() { Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName()); - MethodCacheKey instance = new MethodCacheKey(m, getClass()); + AnnotatedElementKey instance = new AnnotatedElementKey(m, getClass()); assertKeyEquals(instance, instance); } @Test public void equals() { Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName()); - MethodCacheKey first = new MethodCacheKey(m, getClass()); - MethodCacheKey second = new MethodCacheKey(m, getClass()); + AnnotatedElementKey first = new AnnotatedElementKey(m, getClass()); + AnnotatedElementKey second = new AnnotatedElementKey(m, getClass()); assertKeyEquals(first, second); } @@ -53,8 +53,8 @@ public class MethodCacheKeyTests { @Test public void equalsNoTarget() { Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName()); - MethodCacheKey first = new MethodCacheKey(m, null); - MethodCacheKey second = new MethodCacheKey(m, null); + AnnotatedElementKey first = new AnnotatedElementKey(m, null); + AnnotatedElementKey second = new AnnotatedElementKey(m, null); assertKeyEquals(first, second); } @@ -62,13 +62,13 @@ public class MethodCacheKeyTests { @Test public void noTargetClassNotEquals() { Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName()); - MethodCacheKey first = new MethodCacheKey(m, getClass()); - MethodCacheKey second = new MethodCacheKey(m, null); + AnnotatedElementKey first = new AnnotatedElementKey(m, getClass()); + AnnotatedElementKey second = new AnnotatedElementKey(m, null); assertFalse(first.equals(second)); } - protected void assertKeyEquals(MethodCacheKey first, MethodCacheKey second) { + protected void assertKeyEquals(AnnotatedElementKey first, AnnotatedElementKey second) { assertEquals(first, second); assertEquals(first.hashCode(), second.hashCode()); } diff --git a/spring-context/src/test/java/org/springframework/context/expression/CachedExpressionEvaluatorTests.java b/spring-context/src/test/java/org/springframework/context/expression/CachedExpressionEvaluatorTests.java new file mode 100644 index 00000000000..cba42bcc6a0 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/expression/CachedExpressionEvaluatorTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2015 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.expression; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.Test; + +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Stephane Nicoll + */ +public class CachedExpressionEvaluatorTests { + + private final TestExpressionEvaluator expressionEvaluator = new TestExpressionEvaluator(); + + @Test + public void parseNewExpression() { + Method method = ReflectionUtils.findMethod(getClass(), "toString"); + Expression expression = expressionEvaluator.getTestExpression("true", method, getClass()); + hasParsedExpression("true"); + assertEquals(true, expression.getValue()); + assertEquals("Expression should be in cache", 1, expressionEvaluator.testCache.size()); + } + + @Test + public void cacheExpression() { + Method method = ReflectionUtils.findMethod(getClass(), "toString"); + + expressionEvaluator.getTestExpression("true", method, getClass()); + expressionEvaluator.getTestExpression("true", method, getClass()); + expressionEvaluator.getTestExpression("true", method, getClass()); + hasParsedExpression("true"); + assertEquals("Only one expression should be in cache", 1, expressionEvaluator.testCache.size()); + } + + @Test + public void cacheExpressionBasedOnConcreteType() { + Method method = ReflectionUtils.findMethod(getClass(), "toString"); + expressionEvaluator.getTestExpression("true", method, getClass()); + expressionEvaluator.getTestExpression("true", method, Object.class); + assertEquals("Cached expression should be based on type", 2, expressionEvaluator.testCache.size()); + } + + private void hasParsedExpression(String expression) { + verify(expressionEvaluator.getParser(), times(1)).parseExpression(expression); + } + + private static class TestExpressionEvaluator extends CachedExpressionEvaluator { + + private final Map testCache = new ConcurrentHashMap<>(); + + public TestExpressionEvaluator() { + super(mockSpelExpressionParser()); + } + + public Expression getTestExpression(String expression, Method method, Class type) { + return getExpression(this.testCache, new AnnotatedElementKey(method, type), expression); + } + + private static SpelExpressionParser mockSpelExpressionParser() { + SpelExpressionParser parser = new SpelExpressionParser(); + return spy(parser); + } + } + +}