I have the need to cache some the results of some asynchronous computations. In detail, to overcome this issue, I am trying to use Spring 4.3 cache and asynchronous computation features.
As an example, let's take the following code:
@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);
}
}
Is it possible? I mean, will the caching abstraction of Spring handle correctly the objects of type CompletableFuture<User>
? I know that Caffeine Cache has something like that, but I can't understand if Spring uses it if properly configured.
EDIT: I am not interested in the User
object itself, but in the CompletableFuture
that represents the computation.
The community asks me to do some experiments, so I made them. I found that the answer to my question is simple: @Cacheable
and @Async
do not work together if they are placed above the same method.
To be clear, I was not asking for a way to directly make the cache return the object owned by a CompletableFuture
. This is impossible, and if it isn't so, it will break the contract of asynchronous computation of the CompletableFuture
class.
As I said, the two annotations do not work together on the same method. If you think about it, it is obvious. Marking with @Async
is also @Cacheable
means to delegate the whole cache management to different asynchronous threads. If the computation of the value of the CompletableFuture
will take a long time to complete, the value in the cache will be placed after that time by Spring Proxy.
Obviously, there is a workaround. The workaround uses the fact the CompletableFuture
is a promise. Let's have a look at the code below.
@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));
}
}
The trick is to create an incomplete promise and return it immediately from the method marked with the @Cacheable
annotation. The promise will be completed asynchronously by another bean that owns the method marked with the @Async
annotation.
As a bonus, I also implemented a solution that does not use the Spring @Async
annotation, but it uses the factory methods available in the CompletableFuture
class directly.
@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);
}
Anyway, I shared the complete solution to my GitHub problem, spring-cacheable-async.
Finally, the above is a long description of what the Jira SPR-12967 refers to.
I hope it helps. Cheers.
As per SPR-12967, ListenableFuture
(CompletableFuture
) are not supported.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With