我需要缓存一些异步计算的结果。详细地说,为了克服这个问题,我试图使用Spring 4.3缓存和异步计算功能。
举个例子,我们来看下面的代码:
@Service
class AsyncService {
@Async
@Cacheable("users")
CompletableFuture<User> findById(String usedId) {
// Some code that retrieves the user relative to id userId
return CompletableFuture.completedFuture(user);
}
}
可能吗?我的意思是,Spring的缓存抽象是否会正确处理CompletableFuture<User>
类型的对象?我知道Caffeine Cache有类似的东西,但我无法理解Spring是否使用它如果配置得当。
编辑:我对User
对象本身不感兴趣,但在代表计算的CompletableFuture
中。
根据SPR-12967,ListenableFuture
(CompletableFuture
)不受支持。
社区要我做一些实验,所以我做了。我发现我的问题的答案很简单:如果将@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
注释标记的方法返回它。承诺将由另一个bean异步完成,该bean拥有使用@Async
注释标记的方法。
作为奖励,我还实现了一个不使用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所指的内容的详细描述。
希望能帮助到你。干杯。
从理论上讲,它会起作用
@Cacheable
后面的CacheManager的实现不是序列化缓存的对象(比如Hazelcast支持的缓存)CompletableFuture
持有一个州,可以通过调用例如在cancel()
方法中,重要的是API的所有用户都不会乱用缓存对象。否则,可能存在无法再检索Future
中的缓存对象的风险,并且需要缓存驱逐@Cacheable
之前总是被称为@Async
的代理?或者相反?还是取决于?例如,如果之前调用了@Async
,它将在Callable
中触发ForkJoinPool
,然后从缓存中检索另一个对象。在一个类中的方法上添加@Async
注释,在另一个类中在方法级别添加@Cacheable
注释。
然后从服务或任何不同的层调用@Async
方法。
它对我有用,包括Redis缓存和Async,它们大大提高了性能。