From 9b5e47026c61268eda2d8a0bee4cb7bdb72ede1c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 22 Dec 2015 11:05:24 +0100 Subject: [PATCH] Support for bean refs in caching SpEL expressions Issue: SPR-13182 --- .../cache/interceptor/CacheAspectSupport.java | 3 +- .../interceptor/ExpressionEvaluator.java | 14 +++- .../config/EnableCachingIntegrationTests.java | 81 ++++++++++++++++--- .../interceptor/ExpressionEvaluatorTests.java | 26 +++++- 4 files changed, 103 insertions(+), 21 deletions(-) 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 df461d895f..d5fd77867c 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 @@ -705,7 +705,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private EvaluationContext createEvaluationContext(Object result) { return evaluator.createEvaluationContext( - this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result); + this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, + result, applicationContext); } protected Collection getCaches() { 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 88ed1484c5..35b16fdd75 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 @@ -22,8 +22,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cache.Cache; import org.springframework.context.expression.AnnotatedElementKey; +import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.CachedExpressionEvaluator; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; @@ -75,12 +77,12 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { /** * Create an {@link EvaluationContext} without a return value. - * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object) + * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object, BeanFactory) */ public EvaluationContext createEvaluationContext(Collection caches, - Method method, Object[] args, Object target, Class targetClass) { + Method method, Object[] args, Object target, Class targetClass, BeanFactory beanFactory) { - return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT); + return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT, beanFactory); } /** @@ -95,7 +97,8 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { * @return the evaluation context */ public EvaluationContext createEvaluationContext(Collection caches, - Method method, Object[] args, Object target, Class targetClass, Object result) { + Method method, Object[] args, Object target, Class targetClass, Object result, + BeanFactory beanFactory) { CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches, method, args, target, targetClass); @@ -108,6 +111,9 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { else if (result != NO_RESULT) { evaluationContext.setVariable(RESULT_VARIABLE, result); } + if (beanFactory != null) { + evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); + } return evaluationContext; } diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java index d51cc52460..a67e75920d 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java @@ -2,6 +2,7 @@ package org.springframework.cache.config; import java.util.concurrent.atomic.AtomicLong; +import org.junit.After; import org.junit.Test; import org.springframework.cache.Cache; @@ -21,29 +22,37 @@ import org.springframework.context.annotation.Import; import static org.springframework.cache.CacheTestUtils.*; /** - * Tests that represent real use cases with advanced configuration + * Tests that represent real use cases with advanced configuration. + * * @author Stephane Nicoll */ public class EnableCachingIntegrationTests { + private ConfigurableApplicationContext context; + + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + @Test public void fooServiceWithInterface() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(FooConfig.class); - FooService service = context.getBean(FooService.class); - fooGetSimple(context, service); + this.context = new AnnotationConfigApplicationContext(FooConfig.class); + FooService service = this.context.getBean(FooService.class); + fooGetSimple(service); } @Test public void fooServiceWithInterfaceCglib() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(FooConfigCglib.class); - FooService service = context.getBean(FooService.class); - fooGetSimple(context, service); + this.context = new AnnotationConfigApplicationContext(FooConfigCglib.class); + FooService service = this.context.getBean(FooService.class); + fooGetSimple(service); } - private void fooGetSimple(ApplicationContext context, FooService service) { - CacheManager cacheManager = context.getBean(CacheManager.class); - - Cache cache = cacheManager.getCache("testCache"); + private void fooGetSimple(FooService service) { + Cache cache = getCache(); Object key = new Object(); assertCacheMiss(key, cache); @@ -52,6 +61,21 @@ public class EnableCachingIntegrationTests { assertCacheHit(key, value, cache); } + @Test + public void beanCondition() { + this.context = new AnnotationConfigApplicationContext(BeanConditionConfig.class); + Cache cache = getCache(); + FooService service = context.getBean(FooService.class); + + Object key = new Object(); + service.getWithCondition(key); + assertCacheMiss(key, cache); + } + + private Cache getCache() { + return this.context.getBean(CacheManager.class).getCache("testCache"); + } + @Configuration static class SharedConfig extends CachingConfigurerSupport { @Override @@ -81,8 +105,10 @@ public class EnableCachingIntegrationTests { } } - private static interface FooService { - public Object getSimple(Object key); + private interface FooService { + Object getSimple(Object key); + + Object getWithCondition(Object key); } @CacheConfig(cacheNames = "testCache") @@ -94,6 +120,35 @@ public class EnableCachingIntegrationTests { public Object getSimple(Object key) { return counter.getAndIncrement(); } + + @Override + @Cacheable(condition = "@bar.enabled") + public Object getWithCondition(Object key) { + return counter.getAndIncrement(); + } + } + + @Configuration + @Import(FooConfig.class) + @EnableCaching + static class BeanConditionConfig { + + @Bean + public Bar bar() { + return new Bar(false); + } + + static class Bar { + private final boolean enabled; + + public Bar(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + } } } 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 508a21fd8b..4de4a9466c 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 @@ -23,11 +23,15 @@ import java.util.Iterator; import org.junit.Test; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; 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.context.support.StaticApplicationContext; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.ReflectionUtils; @@ -75,7 +79,8 @@ public class ExpressionEvaluatorTests { Object[] args = new Object[] { new Object(), new Object() }; Collection caches = Collections.singleton(new ConcurrentMapCache("test")); - EvaluationContext evalCtx = eval.createEvaluationContext(caches, method, args, target, target.getClass()); + EvaluationContext evalCtx = eval.createEvaluationContext(caches, method, args, + target, target.getClass(), null); Collection ops = getOps("multipleCaching"); Iterator it = ops.iterator(); @@ -122,14 +127,29 @@ public class ExpressionEvaluatorTests { } } + @Test + public void resolveBeanReference() throws Exception { + StaticApplicationContext applicationContext = new StaticApplicationContext(); + BeanDefinition beanDefinition = new RootBeanDefinition(String.class); + applicationContext.registerBeanDefinition("myBean", beanDefinition); + applicationContext.refresh(); + + EvaluationContext context = createEvaluationContext(ExpressionEvaluator.NO_RESULT, applicationContext); + Object value = new SpelExpressionParser().parseExpression("@myBean.class.getName()").getValue(context); + assertThat(value, is(String.class.getName())); + } + private EvaluationContext createEvaluationContext(Object result) { + return createEvaluationContext(result, null); + } + + private EvaluationContext createEvaluationContext(Object result, BeanFactory beanFactory) { AnnotatedClass target = new AnnotatedClass(); Method method = ReflectionUtils.findMethod(AnnotatedClass.class, "multipleCaching", Object.class, Object.class); Object[] args = new Object[] { new Object(), new Object() }; Collection caches = Collections.singleton(new ConcurrentMapCache("test")); - EvaluationContext context = eval.createEvaluationContext(caches, method, args, target, target.getClass(), result); - return context; + return eval.createEvaluationContext(caches, method, args, target, target.getClass(), result, beanFactory); }