Browse Source
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-12622pull/911/head
10 changed files with 265 additions and 104 deletions
@ -0,0 +1,112 @@
@@ -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 |
||||
* <p>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<ExpressionKey, Expression> 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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,89 @@
@@ -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<ExpressionKey, Expression> 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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue