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 f9700754e55..2de8bd8f5c3 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-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +31,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; @@ -37,11 +40,10 @@ import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.cache.Cache; 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.lang.UsesJava8; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -77,17 +79,26 @@ import org.springframework.util.StringUtils; * @since 3.1 */ public abstract class CacheAspectSupport extends AbstractCacheInvoker - implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware { + implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { + + private static Class javaUtilOptionalClass = null; + + static { + try { + javaUtilOptionalClass = + ClassUtils.forName("java.util.Optional", CacheAspectSupport.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + // Java 8 not available - Optional references simply not supported then. + } + } protected final Log logger = LogFactory.getLog(getClass()); - /** - * Cache of CacheOperationMetadata, keyed by {@link CacheOperationCacheKey}. - */ private final Map metadataCache = new ConcurrentHashMap(1024); - private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); + private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator(); private CacheOperationSource cacheOperationSource; @@ -95,7 +106,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private CacheResolver cacheResolver; - private ApplicationContext applicationContext; + private BeanFactory beanFactory; private boolean initialized = false; @@ -164,12 +175,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker return this.cacheResolver; } + /** + * Set the containing {@link BeanFactory} for {@link CacheManager} and other + * service lookups. + * @since 4.3 + */ @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * @deprecated as of 4.3, in favor of {@link #setBeanFactory} + */ + @Deprecated public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; + this.beanFactory = applicationContext; } + @Override public void afterPropertiesSet() { Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " + "If there are no cacheable methods, then don't use a cache aspect."); @@ -181,7 +206,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker if (getCacheResolver() == null) { // Lazily initialize cache resolver via default cache manager... try { - setCacheManager(this.applicationContext.getBean(CacheManager.class)); + setCacheManager(this.beanFactory.getBean(CacheManager.class)); } catch (NoUniqueBeanDefinitionException ex) { throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " + @@ -282,7 +307,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * @see CacheOperation#cacheResolver */ protected T getBean(String beanName, Class expectedType) { - return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext, expectedType, beanName); + return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName); } /** @@ -294,13 +319,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { - // check whether aspect is enabled - // to cope with cases where the AJ is pulled in automatically + // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically) if (this.initialized) { Class targetClass = getTargetClass(target); Collection operations = getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(operations)) { - return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass)); + return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass)); } } @@ -329,12 +353,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker return targetClass; } - private Object execute(final CacheOperationInvoker invoker, CacheOperationContexts contexts) { + private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) { // Special handling of synchronized invocation if (contexts.isSynchronized()) { CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next(); - if (isConditionPassing(context, ExpressionEvaluator.NO_RESULT)) { - Object key = generateKey(context, ExpressionEvaluator.NO_RESULT); + if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { + Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); Cache cache = context.getCaches().iterator().next(); try { return cache.get(key, new Callable() { @@ -358,7 +382,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker // Process any early evictions - processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT); + processCacheEvicts(contexts.get(CacheEvictOperation.class), true, + CacheOperationExpressionEvaluator.NO_RESULT); // Check if we have a cached item matching the conditions Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); @@ -366,42 +391,56 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker // Collect puts from any @Cacheable miss, if no cached item is found List cachePutRequests = new LinkedList(); if (cacheHit == null) { - collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests); + collectPutRequests(contexts.get(CacheableOperation.class), + CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } - Cache.ValueWrapper result = null; + Object cacheValue; + Object returnValue; - // If there are no put requests, just use the cache hit - if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) { - result = cacheHit; + if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) { + // If there are no put requests, just use the cache hit + cacheValue = cacheHit.get(); + if (method.getReturnType() == javaUtilOptionalClass && + (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) { + returnValue = OptionalUnwrapper.wrap(cacheValue); + } + else { + returnValue = cacheValue; + } } - - // Invoke the method if don't have a cache hit - if (result == null) { - result = new SimpleValueWrapper(invokeOperation(invoker)); + else { + // Invoke the method if we don't have a cache hit + returnValue = invokeOperation(invoker); + if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) { + cacheValue = OptionalUnwrapper.unwrap(returnValue); + } + else { + cacheValue = returnValue; + } } // Collect any explicit @CachePuts - collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests); + collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // Process any collected put requests, either from @CachePut or a @Cacheable miss for (CachePutRequest cachePutRequest : cachePutRequests) { - cachePutRequest.apply(result.get()); + cachePutRequest.apply(cacheValue); } // Process any late evictions - processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get()); + processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); - return result.get(); + return returnValue; } private boolean hasCachePut(CacheOperationContexts contexts) { - // Evaluate the conditions *without* the result object because we don't have it yet. + // Evaluate the conditions *without* the result object because we don't have it yet... Collection cachePutContexts = contexts.get(CachePutOperation.class); Collection excluded = new ArrayList(); for (CacheOperationContext context : cachePutContexts) { try { - if (!context.isConditionPassing(ExpressionEvaluator.RESULT_UNAVAILABLE)) { + if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) { excluded.add(context); } } @@ -453,7 +492,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * or {@code null} if none is found */ private Cache.ValueWrapper findCachedItem(Collection contexts) { - Object result = ExpressionEvaluator.NO_RESULT; + Object result = CacheOperationExpressionEvaluator.NO_RESULT; for (CacheOperationContext context : contexts) { if (isConditionPassing(context, result)) { Object key = generateKey(context, result); @@ -551,7 +590,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private boolean determineSyncFlag(Method method) { List cacheOperationContexts = this.contexts.get(CacheableOperation.class); - if (cacheOperationContexts == null) { // No @Cacheable operation + if (cacheOperationContexts == null) { // no @Cacheable operation at all return false; } boolean syncEnabled = false; @@ -563,18 +602,18 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } if (syncEnabled) { if (this.contexts.size() > 1) { - throw new IllegalStateException("@Cacheable(sync = true) cannot be combined with other cache operations on '" + method + "'"); + throw new IllegalStateException("@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'"); } if (cacheOperationContexts.size() > 1) { - throw new IllegalStateException("Only one @Cacheable(sync = true) entry is allowed on '" + method + "'"); + throw new IllegalStateException("Only one @Cacheable(sync=true) entry is allowed on '" + method + "'"); } CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next(); CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation(); if (cacheOperationContext.getCaches().size() > 1) { - throw new IllegalStateException("@Cacheable(sync = true) only allows a single cache on '" + operation + "'"); + throw new IllegalStateException("@Cacheable(sync=true) only allows a single cache on '" + operation + "'"); } if (StringUtils.hasText(operation.getUnless())) { - throw new IllegalStateException("@Cacheable(sync = true) does not support unless attribute on '" + operation + "'"); + throw new IllegalStateException("@Cacheable(sync=true) does not support unless attribute on '" + operation + "'"); } return true; } @@ -702,9 +741,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, applicationContext); + return evaluator.createEvaluationContext(this.caches, this.metadata.method, this.args, + this.target, this.metadata.targetClass, result, beanFactory); } protected Collection getCaches() { @@ -790,4 +828,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } } + + /** + * Inner class to avoid a hard dependency on Java 8. + */ + @UsesJava8 + private static class OptionalUnwrapper { + + public static Object unwrap(Object optionalObject) { + Optional optional = (Optional) optionalObject; + if (!optional.isPresent()) { + return null; + } + Object result = optional.get(); + Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported"); + return result; + } + + public static Object wrap(Object value) { + return Optional.ofNullable(value); + } + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java similarity index 90% rename from spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java rename to spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java index 35b16fdd751..9111bab0ca6 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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,8 +27,6 @@ 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; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -45,7 +43,7 @@ import org.springframework.expression.Expression; * @author Stephane Nicoll * @since 3.1 */ -class ExpressionEvaluator extends CachedExpressionEvaluator { +class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator { /** * Indicate that there is no result variable. @@ -62,8 +60,6 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { */ public static final String RESULT_VARIABLE = "result"; - // shared param discoverer since it caches data internally - private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer(); private final Map keyCache = new ConcurrentHashMap(64); @@ -100,11 +96,11 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { Method method, Object[] args, Object target, Class targetClass, Object result, BeanFactory beanFactory) { - CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches, - method, args, target, targetClass); + CacheExpressionRootObject rootObject = new CacheExpressionRootObject( + caches, method, args, target, targetClass); Method targetMethod = getTargetMethod(targetClass, method); - CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject, - targetMethod, args, this.paramNameDiscoverer); + CacheEvaluationContext evaluationContext = new CacheEvaluationContext( + rootObject, targetMethod, args, getParameterNameDiscoverer()); if (result == RESULT_UNAVAILABLE) { evaluationContext.addUnavailableVariable(RESULT_VARIABLE); } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java index eaa2706d0d5..8901ba1945c 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java @@ -21,8 +21,8 @@ package org.springframework.cache.interceptor; * *

Does not provide a way to transmit checked exceptions but * provide a special exception that should be used to wrap any - * exception that was thrown by the underlying invocation. Callers - * are expected to handle this issue type specifically. + * exception that was thrown by the underlying invocation. + * Callers are expected to handle this issue type specifically. * * @author Stephane Nicoll * @since 4.1 @@ -38,8 +38,9 @@ public interface CacheOperationInvoker { */ Object invoke() throws ThrowableWrapper; + /** - * Wrap any exception thrown while invoking {@link #invoke()} + * Wrap any exception thrown while invoking {@link #invoke()}. */ @SuppressWarnings("serial") class ThrowableWrapper extends RuntimeException { diff --git a/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java index 01452278996..8cb00fe8241 100644 --- a/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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,8 +27,6 @@ import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.CachedExpressionEvaluator; import org.springframework.context.expression.MethodBasedEvaluationContext; -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -42,13 +40,11 @@ import org.springframework.expression.Expression; */ class EventExpressionEvaluator extends CachedExpressionEvaluator { - // shared param discoverer since it caches data internally - private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer(); - private final Map conditionCache = new ConcurrentHashMap(64); private final Map targetMethodCache = new ConcurrentHashMap(64); + /** * Create the suitable {@link EvaluationContext} for the specified event handling * on the specified method. @@ -58,8 +54,8 @@ class EventExpressionEvaluator extends CachedExpressionEvaluator { Method targetMethod = getTargetMethod(targetClass, method); EventExpressionRootObject root = new EventExpressionRootObject(event, args); - MethodBasedEvaluationContext evaluationContext = - new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer); + MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext( + root, targetMethod, args, getParameterNameDiscoverer()); if (beanFactory != null) { evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); } 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 index 6ff1c898767..d530db801c6 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java @@ -18,6 +18,8 @@ package org.springframework.context.expression; import java.util.Map; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -35,12 +37,14 @@ public abstract class CachedExpressionEvaluator { private final SpelExpressionParser parser; + private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + /** * Create a new instance with the specified {@link SpelExpressionParser}. */ protected CachedExpressionEvaluator(SpelExpressionParser parser) { - Assert.notNull(parser, "Parser must not be null"); + Assert.notNull(parser, "SpelExpressionParser must not be null"); this.parser = parser; } @@ -59,6 +63,14 @@ public abstract class CachedExpressionEvaluator { return this.parser; } + /** + * Return a shared parameter name discoverer which caches data internally. + * @since 4.3 + */ + protected ParameterNameDiscoverer getParameterNameDiscoverer() { + return this.parameterNameDiscoverer; + } + /** * Return the {@link Expression} for the specified SpEL value diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java index 84d6bc7696a..06729f9eb9a 100644 --- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java +++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -20,12 +20,14 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mockito; +import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.cache.annotation.CachingConfigurerSupport; @@ -39,6 +41,7 @@ import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.tests.sample.beans.TestBean; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -83,11 +86,11 @@ public class CacheReproTests { String key = "1"; Object result = bean.getSimple("1"); - verify(cache, times(1)).get(key); // first call: cache miss + verify(cache, times(1)).get(key); // first call: cache miss Object cachedResult = bean.getSimple("1"); assertSame(result, cachedResult); - verify(cache, times(2)).get(key); // second call: cache hit + verify(cache, times(2)).get(key); // second call: cache hit context.close(); } @@ -100,11 +103,11 @@ public class CacheReproTests { String key = "1"; Object result = bean.getNeverCache("1"); - verify(cache, times(0)).get(key); // no cache hit at all, caching disabled + verify(cache, times(0)).get(key); // no cache hit at all, caching disabled Object cachedResult = bean.getNeverCache("1"); assertNotSame(result, cachedResult); - verify(cache, times(0)).get(key); // caching disabled + verify(cache, times(0)).get(key); // caching disabled context.close(); } @@ -114,8 +117,9 @@ public class CacheReproTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr13081Config.class); MyCacheResolver cacheResolver = context.getBean(MyCacheResolver.class); Spr13081Service bean = context.getBean(Spr13081Service.class); + assertNull(cacheResolver.getCache("foo").get("foo")); - Object result = bean.getSimple("foo"); // cache name = id + Object result = bean.getSimple("foo"); // cache name = id assertEquals(result, cacheResolver.getCache("foo").get("foo").get()); } @@ -124,12 +128,21 @@ public class CacheReproTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr13081Config.class); Spr13081Service bean = context.getBean(Spr13081Service.class); - thrown.expect(IllegalStateException.class); thrown.expectMessage(MyCacheResolver.class.getName()); bean.getSimple(null); } + @Test + public void spr14230AdaptsToOptional() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr14230Config.class); + Spr14230Service bean = context.getBean(Spr14230Service.class); + + TestBean tb = new TestBean("tb1"); + bean.insertItem(tb); + assertSame(tb, bean.findById("tb1").get()); + } + @Configuration @EnableCaching @@ -245,6 +258,7 @@ public class CacheReproTests { } } + @Configuration @EnableCaching public static class Spr13081Config extends CachingConfigurerSupport { @@ -259,9 +273,9 @@ public class CacheReproTests { public Spr13081Service service() { return new Spr13081Service(); } - } + public static class MyCacheResolver extends AbstractCacheResolver { public MyCacheResolver() { @@ -282,6 +296,7 @@ public class CacheReproTests { } } + public static class Spr13081Service { @Cacheable @@ -290,4 +305,34 @@ public class CacheReproTests { } } + + public static class Spr14230Service { + + @Cacheable("itemCache") + public Optional findById(String id) { + return Optional.of(new TestBean(id)); + } + + @CachePut(cacheNames = "itemCache", key = "#item.name") + public TestBean insertItem(TestBean item) { + return item; + } + } + + + @Configuration + @EnableCaching + public static class Spr14230Config { + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(); + } + + @Bean + public Spr14230Service service() { + return new Spr14230Service(); + } + } + } diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java index c21d5fb35a8..531bc01ad59 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -67,35 +67,35 @@ public class CacheSyncFailureTests { @Test public void unlessSync() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("@Cacheable(sync = true) does not support unless attribute"); + thrown.expectMessage("@Cacheable(sync=true) does not support unless attribute"); this.simpleService.unlessSync("key"); } @Test public void severalCachesSync() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("@Cacheable(sync = true) only allows a single cache"); + thrown.expectMessage("@Cacheable(sync=true) only allows a single cache"); this.simpleService.severalCachesSync("key"); } @Test public void severalCachesWithResolvedSync() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("@Cacheable(sync = true) only allows a single cache"); + thrown.expectMessage("@Cacheable(sync=true) only allows a single cache"); this.simpleService.severalCachesWithResolvedSync("key"); } @Test public void syncWithAnotherOperation() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("@Cacheable(sync = true) cannot be combined with other cache operations"); + thrown.expectMessage("@Cacheable(sync=true) cannot be combined with other cache operations"); this.simpleService.syncWithAnotherOperation("key"); } @Test public void syncWithTwoGetOperations() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("Only one @Cacheable(sync = true) entry is allowed"); + thrown.expectMessage("Only one @Cacheable(sync=true) entry is allowed"); this.simpleService.syncWithTwoGetOperations("key"); } 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 4de4a9466cf..6e3b5633516 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-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -47,15 +47,17 @@ import static org.junit.Assert.*; */ public class ExpressionEvaluatorTests { - private ExpressionEvaluator eval = new ExpressionEvaluator(); + private final CacheOperationExpressionEvaluator eval = new CacheOperationExpressionEvaluator(); + + private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource(); - private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource(); private Collection getOps(String name) { Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class); - return source.getCacheOperations(method, AnnotatedClass.class); + return this.source.getCacheOperations(method, AnnotatedClass.class); } + @Test public void testMultipleCachingSource() throws Exception { Collection ops = getOps("multipleCaching"); @@ -110,14 +112,14 @@ public class ExpressionEvaluatorTests { @Test public void withoutReturnValue() throws Exception { - EvaluationContext context = createEvaluationContext(ExpressionEvaluator.NO_RESULT); + EvaluationContext context = createEvaluationContext(CacheOperationExpressionEvaluator.NO_RESULT); Object value = new SpelExpressionParser().parseExpression("#result").getValue(context); assertThat(value, nullValue()); } @Test public void unavailableReturnValue() throws Exception { - EvaluationContext context = createEvaluationContext(ExpressionEvaluator.RESULT_UNAVAILABLE); + EvaluationContext context = createEvaluationContext(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE); try { new SpelExpressionParser().parseExpression("#result").getValue(context); fail("Should have failed to parse expression, result not available"); @@ -134,7 +136,7 @@ public class ExpressionEvaluatorTests { applicationContext.registerBeanDefinition("myBean", beanDefinition); applicationContext.refresh(); - EvaluationContext context = createEvaluationContext(ExpressionEvaluator.NO_RESULT, applicationContext); + EvaluationContext context = createEvaluationContext(CacheOperationExpressionEvaluator.NO_RESULT, applicationContext); Object value = new SpelExpressionParser().parseExpression("@myBean.class.getName()").getValue(context); assertThat(value, is(String.class.getName())); }