社区要求我做一些实验,所以我做了。我发现我的问题的答案很简单:@Cacheable 和 @Async 如果放在同一个方法之上,就不能一起工作。
需要明确的是,我并不是在寻求一种直接使缓存返回 CompletableFuture 拥有的对象的方法。这是不可能的,如果不是这样,它会破坏CompletableFuture 类的异步计算的契约。
正如我所说,这两个注释不能在同一个方法上一起使用。如果你仔细想想,这很明显。用@Async 标记也是@Cacheable 意味着将整个缓存管理委托给不同的异步线程。如果CompletableFuture的值的计算需要很长时间才能完成,那么缓存中的值将在此之后由Spring Proxy放置。
显然,有一种解决方法。解决方法使用CompletableFuture 是一个承诺这一事实。让我们看看下面的代码。
@Component
public class CachedService {
/* Dependecies resolution code */
private final AsyncService service;
@Cacheable(cacheNames = "ints")
public CompletableFuture<Integer> randomIntUsingSpringAsync() throws InterruptedException {
final CompletableFuture<Integer> promise = new CompletableFuture<>();
// Letting an asynchronous method to complete the promise in the future
service.performTask(promise);
// Returning the promise immediately
return promise;
}
}
@Component
public class AsyncService {
@Async
void performTask(CompletableFuture<Integer> promise) throws InterruptedException {
Thread.sleep(2000);
// Completing the promise asynchronously
promise.complete(random.nextInt(1000));
}
}
诀窍是创建一个不完整的 Promise 并立即从标有 @Cacheable 注释的方法中返回它。 Promise 将由另一个拥有 @Async 注解标记的方法的 bean 异步完成。
作为奖励,我还实现了一个不使用 Spring @Async 注释的解决方案,但它直接使用了 CompletableFuture 类中可用的工厂方法。
@Cacheable(cacheNames = "ints1")
public CompletableFuture<Integer> randomIntNativelyAsync() throws
InterruptedException {
return CompletableFuture.supplyAsync(this::getAsyncInteger, executor);
}
private Integer getAsyncInteger() {
logger.info("Entering performTask");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return random.nextInt(1000);
}
无论如何,我分享了我的 GitHub 问题的完整解决方案,spring-cacheable-async。
最后,上面是对 Jira SPR-12967 所指的内容的详细描述。
我希望它有所帮助。
干杯。