Add caching annotation support for CompletableFuture and reactive return values
Includes CompletableFuture-based retrieve operations on Spring's Cache interface. Includes support for retrieve operations on CaffeineCache and ConcurrentMapCache. Includes async cache mode option on CaffeineCacheManager. Closes gh-17559 Closes gh-17920 Closes gh-30122
This commit is contained in:
@@ -20,11 +20,15 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cache.annotation.Caching;
|
||||
@@ -118,6 +122,7 @@ class CacheReproTests {
|
||||
assertThat(cacheResolver.getCache("foo").get("foo")).isNull();
|
||||
Object result = bean.getSimple("foo"); // cache name = id
|
||||
assertThat(cacheResolver.getCache("foo").get("foo").get()).isEqualTo(result);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -127,7 +132,7 @@ class CacheReproTests {
|
||||
Spr13081Service bean = context.getBean(Spr13081Service.class);
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> bean.getSimple(null))
|
||||
.withMessageContaining(MyCacheResolver.class.getName());
|
||||
.withMessageContaining(MyCacheResolver.class.getName());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -146,6 +151,7 @@ class CacheReproTests {
|
||||
TestBean tb2 = bean.findById("tb1").get();
|
||||
assertThat(tb2).isNotSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb2);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -164,6 +170,151 @@ class CacheReproTests {
|
||||
TestBean tb2 = bean.findById("tb1").get();
|
||||
assertThat(tb2).isNotSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb2);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void spr14235AdaptsToCompletableFuture() {
|
||||
AnnotationConfigApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(Spr14235Config.class, Spr14235FutureService.class);
|
||||
Spr14235FutureService bean = context.getBean(Spr14235FutureService.class);
|
||||
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||
|
||||
TestBean tb = bean.findById("tb1").join();
|
||||
assertThat(bean.findById("tb1").join()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
bean.clear().join();
|
||||
TestBean tb2 = bean.findById("tb1").join();
|
||||
assertThat(tb2).isNotSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb2);
|
||||
|
||||
bean.clear().join();
|
||||
bean.insertItem(tb).join();
|
||||
assertThat(bean.findById("tb1").join()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void spr14235AdaptsToCompletableFutureWithSync() throws Exception {
|
||||
AnnotationConfigApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(Spr14235Config.class, Spr14235FutureServiceSync.class);
|
||||
Spr14235FutureServiceSync bean = context.getBean(Spr14235FutureServiceSync.class);
|
||||
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||
|
||||
TestBean tb = bean.findById("tb1").get();
|
||||
assertThat(bean.findById("tb1").get()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
cache.clear();
|
||||
TestBean tb2 = bean.findById("tb1").get();
|
||||
assertThat(tb2).isNotSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb2);
|
||||
|
||||
cache.clear();
|
||||
bean.insertItem(tb);
|
||||
assertThat(bean.findById("tb1").get()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void spr14235AdaptsToReactorMono() {
|
||||
AnnotationConfigApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(Spr14235Config.class, Spr14235MonoService.class);
|
||||
Spr14235MonoService bean = context.getBean(Spr14235MonoService.class);
|
||||
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||
|
||||
TestBean tb = bean.findById("tb1").block();
|
||||
assertThat(bean.findById("tb1").block()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
bean.clear().block();
|
||||
TestBean tb2 = bean.findById("tb1").block();
|
||||
assertThat(tb2).isNotSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb2);
|
||||
|
||||
bean.clear().block();
|
||||
bean.insertItem(tb).block();
|
||||
assertThat(bean.findById("tb1").block()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void spr14235AdaptsToReactorMonoWithSync() {
|
||||
AnnotationConfigApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(Spr14235Config.class, Spr14235MonoServiceSync.class);
|
||||
Spr14235MonoServiceSync bean = context.getBean(Spr14235MonoServiceSync.class);
|
||||
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||
|
||||
TestBean tb = bean.findById("tb1").block();
|
||||
assertThat(bean.findById("tb1").block()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
cache.clear();
|
||||
TestBean tb2 = bean.findById("tb1").block();
|
||||
assertThat(tb2).isNotSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb2);
|
||||
|
||||
cache.clear();
|
||||
bean.insertItem(tb);
|
||||
assertThat(bean.findById("tb1").block()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void spr14235AdaptsToReactorFlux() {
|
||||
AnnotationConfigApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(Spr14235Config.class, Spr14235FluxService.class);
|
||||
Spr14235FluxService bean = context.getBean(Spr14235FluxService.class);
|
||||
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||
|
||||
List<TestBean> tb = bean.findById("tb1").collectList().block();
|
||||
assertThat(bean.findById("tb1").collectList().block()).isEqualTo(tb);
|
||||
assertThat(cache.get("tb1").get()).isEqualTo(tb);
|
||||
|
||||
bean.clear().blockLast();
|
||||
List<TestBean> tb2 = bean.findById("tb1").collectList().block();
|
||||
assertThat(tb2).isNotEqualTo(tb);
|
||||
assertThat(cache.get("tb1").get()).isEqualTo(tb2);
|
||||
|
||||
bean.clear().blockLast();
|
||||
bean.insertItem("tb1", tb).blockLast();
|
||||
assertThat(bean.findById("tb1").collectList().block()).isEqualTo(tb);
|
||||
assertThat(cache.get("tb1").get()).isEqualTo(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void spr14235AdaptsToReactorFluxWithSync() {
|
||||
AnnotationConfigApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(Spr14235Config.class, Spr14235FluxServiceSync.class);
|
||||
Spr14235FluxServiceSync bean = context.getBean(Spr14235FluxServiceSync.class);
|
||||
Cache cache = context.getBean(CacheManager.class).getCache("itemCache");
|
||||
|
||||
List<TestBean> tb = bean.findById("tb1").collectList().block();
|
||||
assertThat(bean.findById("tb1").collectList().block()).isEqualTo(tb);
|
||||
assertThat(cache.get("tb1").get()).isEqualTo(tb);
|
||||
|
||||
cache.clear();
|
||||
List<TestBean> tb2 = bean.findById("tb1").collectList().block();
|
||||
assertThat(tb2).isNotEqualTo(tb);
|
||||
assertThat(cache.get("tb1").get()).isEqualTo(tb2);
|
||||
|
||||
cache.clear();
|
||||
bean.insertItem("tb1", tb);
|
||||
assertThat(bean.findById("tb1").collectList().block()).isEqualTo(tb);
|
||||
assertThat(cache.get("tb1").get()).isEqualTo(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -177,6 +328,7 @@ class CacheReproTests {
|
||||
bean.insertItem(tb);
|
||||
assertThat(bean.findById("tb1").get()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -190,6 +342,7 @@ class CacheReproTests {
|
||||
bean.insertItem(tb);
|
||||
assertThat(bean.findById("tb1").get()).isSameAs(tb);
|
||||
assertThat(cache.get("tb1").get()).isSameAs(tb);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -387,6 +540,120 @@ class CacheReproTests {
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14235FutureService {
|
||||
|
||||
@Cacheable(value = "itemCache")
|
||||
public CompletableFuture<TestBean> findById(String id) {
|
||||
return CompletableFuture.completedFuture(new TestBean(id));
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "itemCache", key = "#item.name")
|
||||
public CompletableFuture<TestBean> insertItem(TestBean item) {
|
||||
return CompletableFuture.completedFuture(item);
|
||||
}
|
||||
|
||||
@CacheEvict(cacheNames = "itemCache", allEntries = true)
|
||||
public CompletableFuture<Void> clear() {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14235FutureServiceSync {
|
||||
|
||||
@Cacheable(value = "itemCache", sync = true)
|
||||
public CompletableFuture<TestBean> findById(String id) {
|
||||
return CompletableFuture.completedFuture(new TestBean(id));
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "itemCache", key = "#item.name")
|
||||
public TestBean insertItem(TestBean item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14235MonoService {
|
||||
|
||||
@Cacheable(value = "itemCache")
|
||||
public Mono<TestBean> findById(String id) {
|
||||
return Mono.just(new TestBean(id));
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "itemCache", key = "#item.name")
|
||||
public Mono<TestBean> insertItem(TestBean item) {
|
||||
return Mono.just(item);
|
||||
}
|
||||
|
||||
@CacheEvict(cacheNames = "itemCache", allEntries = true)
|
||||
public Mono<Void> clear() {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14235MonoServiceSync {
|
||||
|
||||
@Cacheable(value = "itemCache", sync = true)
|
||||
public Mono<TestBean> findById(String id) {
|
||||
return Mono.just(new TestBean(id));
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "itemCache", key = "#item.name")
|
||||
public TestBean insertItem(TestBean item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14235FluxService {
|
||||
|
||||
private int counter = 0;
|
||||
|
||||
@Cacheable(value = "itemCache")
|
||||
public Flux<TestBean> findById(String id) {
|
||||
return Flux.just(new TestBean(id), new TestBean(id + (counter++)));
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "itemCache", key = "#id")
|
||||
public Flux<TestBean> insertItem(String id, List<TestBean> item) {
|
||||
return Flux.fromIterable(item);
|
||||
}
|
||||
|
||||
@CacheEvict(cacheNames = "itemCache", allEntries = true)
|
||||
public Flux<Void> clear() {
|
||||
return Flux.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14235FluxServiceSync {
|
||||
|
||||
private int counter = 0;
|
||||
|
||||
@Cacheable(value = "itemCache", sync = true)
|
||||
public Flux<TestBean> findById(String id) {
|
||||
return Flux.just(new TestBean(id), new TestBean(id + (counter++)));
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "itemCache", key = "#id")
|
||||
public List<TestBean> insertItem(String id, List<TestBean> item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public static class Spr14235Config {
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return new ConcurrentMapCacheManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Spr14853Service {
|
||||
|
||||
@Cacheable(value = "itemCache", sync = true)
|
||||
|
||||
Reference in New Issue
Block a user