diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index a4afb1bdf5d7..17e9da6afb0f 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -16,12 +16,14 @@ package org.springframework.cache.config; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.scheduling.annotation.AsyncResult; /** * @author Costin Leau @@ -38,6 +40,11 @@ public Object cache(Object arg1) { return counter.getAndIncrement(); } + @Override + public Future cacheFuture(Object arg1) { + return new AsyncResult<>(counter.getAndIncrement()); + } + @Override public Object conditional(int field) { return null; diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java index e77a609f0fbc..2a6fed682563 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java @@ -16,6 +16,8 @@ package org.springframework.cache.config; +import java.util.concurrent.Future; + /** * Basic service interface. * @@ -26,6 +28,8 @@ public interface CacheableService { T cache(Object arg1); + Future cacheFuture(Object arg1); + void invalidate(Object arg1); void evictEarly(Object arg1); diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index 1b2fc20941a0..c9b058de13ee 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -22,6 +22,9 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.scheduling.annotation.AsyncResult; + +import java.util.concurrent.Future; /** * Simple cacheable service @@ -40,6 +43,11 @@ public Long cache(Object arg1) { return counter.getAndIncrement(); } + @Override + public Future cacheFuture(Object arg1) { + return new AsyncResult<>(counter.getAndIncrement()); + } + @Override @CacheEvict("testCache") public void invalidate(Object arg1) { 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 ecec4ffda92a..427722b7c812 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 @@ -16,6 +16,7 @@ package org.springframework.cache.interceptor; +import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; @@ -41,6 +42,7 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.expression.EvaluationContext; +import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -48,6 +50,8 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; /** * Base class for caching aspects, such as the {@link CacheInterceptor} @@ -335,6 +339,10 @@ private Object execute(CacheOperationInvoker invoker, CacheOperationContexts con // Check if we have a cached item matching the conditions Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); + if (cacheHit != null && cacheHit.get() instanceof ListenableFutureCacheValueWrapper) { + cacheHit = new SimpleValueWrapper(new AsyncResult<>(((ListenableFutureCacheValueWrapper) cacheHit.get()).getValue())); + } + // Collect puts from any @Cacheable miss, if no cached item is found List cachePutRequests = new LinkedList(); if (cacheHit == null) { @@ -353,6 +361,24 @@ private Object execute(CacheOperationInvoker invoker, CacheOperationContexts con result = new SimpleValueWrapper(invokeOperation(invoker)); } + if (result.get() instanceof ListenableFuture) { + ((ListenableFuture) result.get()).addCallback(new ListenableFutureCallback() { + @Override + public void onSuccess(Object result) { + postProcessCache(contexts, cachePutRequests, new SimpleValueWrapper(new ListenableFutureCacheValueWrapper(result))); + } + @Override + public void onFailure(Throwable ex) { + + } + }); + } else { + postProcessCache(contexts, cachePutRequests, result); + } + return result.get(); + } + + private void postProcessCache(CacheOperationContexts contexts, List cachePutRequests, Cache.ValueWrapper result) { // Collect any explicit @CachePuts collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests); @@ -363,8 +389,6 @@ private Object execute(CacheOperationInvoker invoker, CacheOperationContexts con // Process any late evictions processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get()); - - return result.get(); } private boolean hasCachePut(CacheOperationContexts contexts) { @@ -710,4 +734,20 @@ public int hashCode() { return (this.cacheOperation.hashCode() * 31 + this.methodCacheKey.hashCode()); } } + + + /** + * We have to see if the value retrieved from cache should be converted to ListenableFuture. + */ + private static final class ListenableFutureCacheValueWrapper implements Serializable { + private final Object value; + + private ListenableFutureCacheValueWrapper(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + } } diff --git a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java index f074347af153..7783c2497717 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java @@ -82,6 +82,17 @@ public void testCacheable(CacheableService service) throws Exception { assertSame(r1, r3); } + public void testCacheableFuture(CacheableService service) throws Exception { + Object o1 = new Object(); + + Object r1 = service.cacheFuture(o1).get(); + Object r2 = service.cacheFuture(o1).get(); + Object r3 = service.cacheFuture(o1).get(); + + assertSame(r1, r2); + assertSame(r1, r3); + } + public void testEvict(CacheableService service) throws Exception { Object o1 = new Object(); @@ -450,6 +461,11 @@ public void testCacheable() throws Exception { testCacheable(cs); } + @Test + public void testCacheableFuture() throws Exception { + testCacheableFuture(cs); + } + @Test public void testInvalidate() throws Exception { testEvict(cs); @@ -510,6 +526,11 @@ public void testClassCacheCacheable() throws Exception { testCacheable(ccs); } + @Test + public void testClassCacheCacheableFuture() throws Exception { + testCacheableFuture(ccs); + } + @Test public void testClassCacheInvalidate() throws Exception { testEvict(ccs); diff --git a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index 88c75a0e5744..8cf3e846d105 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -16,12 +16,14 @@ package org.springframework.cache.config; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.scheduling.annotation.AsyncResult; /** * @author Costin Leau @@ -40,6 +42,11 @@ public Object cache(Object arg1) { return counter.getAndIncrement(); } + @Override + public Future cacheFuture(Object arg1) { + return new AsyncResult<>(counter.getAndIncrement()); + } + @Override public Object conditional(int field) { return null; diff --git a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java index 8abd12eee654..fc8a73f8a510 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java @@ -16,6 +16,8 @@ package org.springframework.cache.config; +import java.util.concurrent.Future; + /** * Basic service interface. * @@ -27,6 +29,8 @@ public interface CacheableService { T cache(Object arg1); + Future cacheFuture(Object arg1); + void invalidate(Object arg1); void evictEarly(Object arg1); diff --git a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index ebeec4f55080..625f8d383ee9 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -22,6 +22,9 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.scheduling.annotation.AsyncResult; + +import java.util.concurrent.Future; /** * Simple cacheable service @@ -41,6 +44,12 @@ public Long cache(Object arg1) { return counter.getAndIncrement(); } + @Override + @Cacheable("testCache") + public Future cacheFuture(Object arg1) { + return new AsyncResult<>(counter.getAndIncrement()); + } + @Override @CacheEvict("testCache") public void invalidate(Object arg1) { diff --git a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml index 8b6e61ec48e1..1a449a9db572 100644 --- a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml +++ b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml @@ -10,6 +10,7 @@ + @@ -61,6 +62,7 @@ +