社区要求我做一些实验,所以我就做了。我发现答案很简单:如果在同一个方法上方放置了
@Cacheable
和
@Async
,它们就无法一起工作。
明确一下,我并不是在寻找直接使缓存返回
CompletableFuture
拥有的对象的方法。这是不可能的,如果这样做,它将违反
CompletableFuture
类的
异步计算契约。
正如我所说的,这两个注释不能在同一个方法上一起使用。如果你深思熟虑一下,这是显而易见的。用
@Async
标记也就意味着
@Cacheable
将整个缓存管理委托给不同的异步线程。如果
CompletableFuture
的值计算需要很长时间才能完成,那么Spring代理就会在那段时间之后将该值放入缓存中。
显然,有一个解决方法。解决方法利用了
CompletableFuture
是一个
promise的事实。让我们看一下下面的代码。
@Component
public class CachedService {
private final AsyncService service;
@Cacheable(cacheNames = "ints")
public CompletableFuture<Integer> randomIntUsingSpringAsync() throws InterruptedException {
final CompletableFuture<Integer> promise = new CompletableFuture<>();
service.performTask(promise);
return promise;
}
}
@Component
public class AsyncService {
@Async
void performTask(CompletableFuture<Integer> promise) throws InterruptedException {
Thread.sleep(2000);
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 的描述。
希望能有所帮助。
干杯。
@Async
任务中的Future
与CompatibleFuture
之间的区别,这在4.2
中已经得到了修复。 - Kayaman