Allow @Cacheable method to return Optional
This commit further refines 240f254 to also support java.util.Optional
for synchronized cache access (i.e. when the `sync` attribute on
`@Cacheable` is set to `true`).
Issue: SPR-14853
This commit is contained in:
parent
ffa728c23c
commit
56c48623fd
|
|
@ -40,7 +40,6 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||||
import org.springframework.cache.Cache;
|
import org.springframework.cache.Cache;
|
||||||
import org.springframework.cache.CacheManager;
|
import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.expression.AnnotatedElementKey;
|
import org.springframework.context.expression.AnnotatedElementKey;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -340,12 +339,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||||
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
|
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
|
||||||
Cache cache = context.getCaches().iterator().next();
|
Cache cache = context.getCaches().iterator().next();
|
||||||
try {
|
try {
|
||||||
return cache.get(key, new Callable<Object>() {
|
return wrapCacheValue(method, cache.get(key, new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
return invokeOperation(invoker);
|
return unwrapReturnValue(invokeOperation(invoker));
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
catch (Cache.ValueRetrievalException ex) {
|
catch (Cache.ValueRetrievalException ex) {
|
||||||
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
|
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
|
||||||
|
|
@ -380,18 +379,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||||
if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
|
if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
|
||||||
// If there are no put requests, just use the cache hit
|
// If there are no put requests, just use the cache hit
|
||||||
cacheValue = cacheHit.get();
|
cacheValue = cacheHit.get();
|
||||||
if (method.getReturnType() == Optional.class &&
|
returnValue = wrapCacheValue(method, cacheValue);
|
||||||
(cacheValue == null || cacheValue.getClass() != Optional.class)) {
|
|
||||||
returnValue = Optional.ofNullable(cacheValue);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
returnValue = cacheValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Invoke the method if we don't have a cache hit
|
// Invoke the method if we don't have a cache hit
|
||||||
returnValue = invokeOperation(invoker);
|
returnValue = invokeOperation(invoker);
|
||||||
cacheValue = ObjectUtils.unwrapOptional(returnValue);
|
cacheValue = unwrapReturnValue(returnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect any explicit @CachePuts
|
// Collect any explicit @CachePuts
|
||||||
|
|
@ -408,6 +401,18 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object wrapCacheValue(Method method, Object cacheValue) {
|
||||||
|
if (method.getReturnType() == Optional.class &&
|
||||||
|
(cacheValue == null || cacheValue.getClass() != Optional.class)) {
|
||||||
|
return Optional.ofNullable(cacheValue);
|
||||||
|
}
|
||||||
|
return cacheValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object unwrapReturnValue(Object returnValue) {
|
||||||
|
return ObjectUtils.unwrapOptional(returnValue);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasCachePut(CacheOperationContexts contexts) {
|
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<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class);
|
Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class);
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,22 @@ public class CacheReproTests {
|
||||||
assertSame(tb2, cache.get("tb1").get());
|
assertSame(tb2, cache.get("tb1").get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void spr14853AdaptsToOptionalWithSync() {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr14853Config.class);
|
||||||
|
Spr14853Service bean = context.getBean(Spr14853Service.class);
|
||||||
|
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||||
|
|
||||||
|
TestBean tb = new TestBean("tb1");
|
||||||
|
bean.insertItem(tb);
|
||||||
|
assertSame(tb, bean.findById("tb1").get());
|
||||||
|
assertSame(tb, cache.get("tb1").get());
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
TestBean tb2 = bean.findById("tb1").get();
|
||||||
|
assertNotSame(tb, tb2);
|
||||||
|
assertSame(tb2, cache.get("tb1").get());
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
|
|
@ -341,4 +357,33 @@ public class CacheReproTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Spr14853Service {
|
||||||
|
|
||||||
|
@Cacheable(value = "itemCache", sync = true)
|
||||||
|
public Optional<TestBean> 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 Spr14853Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CacheManager cacheManager() {
|
||||||
|
return new ConcurrentMapCacheManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Spr14853Service service() {
|
||||||
|
return new Spr14853Service();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue