Support for multi-threaded cache access
Previously, if a `@Cacheable` method was accessed with the same key by multiple threads, the underlying method was invoked several times instead of blocking the threads while the value is computed. This scenario typically affects users that enable caching to avoid calling a costly method too often. When said method can be invoked by an arbitrary number of clients on startup, caching has close to no effect. This commit adds a new method on `Cache` that implements the read-through pattern: ``` <T> T get(Object key, Callable<T> valueLoader); ``` If an entry for a given key is not found, the specified `Callable` is invoked to "load" the value and cache it before returning it to the caller. Because the entire operation is managed by the underlying cache provider, it is much more easier to guarantee that the loader (e.g. the annotated method) will be called only once in case of concurrent access. A new `sync` attribute to the `@Cacheable` annotation has been addded. When this flag is enabled, the caching abstraction invokes the new `Cache` method define above. This new mode bring a set of limitations: * It can't be combined with other cache operations * Only one `@Cacheable` operation can be specified * Only one cache is allowed * `condition` and `unless` attribute are not supported The rationale behind those limitations is that the underlying Cache is taking care of the actual caching operation so we can't really apply any SpEL or multiple caches handling there. Issue: SPR-9254
This commit is contained in:
@@ -27,6 +27,7 @@ import org.springframework.cache.annotation.Caching;
|
||||
/**
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Cacheable("testCache")
|
||||
public class AnnotatedClassCacheableService implements CacheableService<Object> {
|
||||
@@ -44,11 +45,28 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Object cacheSync(Object arg1) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Object cacheSyncNull(Object arg1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object conditional(int field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object conditionalSync(int field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object unless(int arg) {
|
||||
return arg;
|
||||
@@ -168,6 +186,18 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
|
||||
throw new UnsupportedOperationException(arg1.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Object throwCheckedSync(Object arg1) throws Exception {
|
||||
throw new IOException(arg1.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Object throwUncheckedSync(Object arg1) {
|
||||
throw new UnsupportedOperationException(arg1.toString());
|
||||
}
|
||||
|
||||
// multi annotations
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.springframework.cache.config;
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public interface CacheableService<T> {
|
||||
|
||||
@@ -28,6 +29,10 @@ public interface CacheableService<T> {
|
||||
|
||||
T cacheNull(Object arg1);
|
||||
|
||||
T cacheSync(Object arg1);
|
||||
|
||||
T cacheSyncNull(Object arg1);
|
||||
|
||||
void invalidate(Object arg1);
|
||||
|
||||
void evictEarly(Object arg1);
|
||||
@@ -42,6 +47,8 @@ public interface CacheableService<T> {
|
||||
|
||||
T conditional(int field);
|
||||
|
||||
T conditionalSync(int field);
|
||||
|
||||
T unless(int arg);
|
||||
|
||||
T key(Object arg1, Object arg2);
|
||||
@@ -72,6 +79,10 @@ public interface CacheableService<T> {
|
||||
|
||||
T throwUnchecked(Object arg1);
|
||||
|
||||
T throwCheckedSync(Object arg1) throws Exception;
|
||||
|
||||
T throwUncheckedSync(Object arg1);
|
||||
|
||||
// multi annotations
|
||||
T multiCache(Object arg1);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.cache.annotation.Caching;
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class DefaultCacheableService implements CacheableService<Long> {
|
||||
|
||||
@@ -47,6 +48,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Long cacheSync(Object arg1) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Long cacheSyncNull(Object arg1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict("testCache")
|
||||
public void invalidate(Object arg1) {
|
||||
@@ -81,11 +94,17 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", condition = "#classField == 3")
|
||||
@Cacheable(cacheNames = "testCache", condition = "#p0 == 3")
|
||||
public Long conditional(int classField) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true, condition = "#p0 == 3")
|
||||
public Long conditionalSync(int field) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", unless = "#result > 10")
|
||||
public Long unless(int arg) {
|
||||
@@ -99,7 +118,7 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable("testCache")
|
||||
@Cacheable(cacheNames = "testCache")
|
||||
public Long varArgsKey(Object... args) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
@@ -176,6 +195,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
throw new UnsupportedOperationException(arg1.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Long throwCheckedSync(Object arg1) throws Exception {
|
||||
throw new IOException(arg1.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "testCache", sync = true)
|
||||
public Long throwUncheckedSync(Object arg1) {
|
||||
throw new UnsupportedOperationException(arg1.toString());
|
||||
}
|
||||
|
||||
// multi annotations
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user