Spring @Cacheable和@Async注释

时间:2017-11-07 14:38:39

标签: java spring spring-cache completable-future spring-async

我需要缓存一些异步计算的结果。详细地说,为了克服这个问题,我试图使用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感兴趣。

5 个答案:

答案 0 :(得分:7)

社区要我做一些实验,所以我做了。我发现我的问题的答案很简单:@Cacheable@Async如果放在同一方法之上,就无法合作。

为了清楚起见,我没有想办法让缓存直接返回CompletableFuture所拥有的对象。这是不可能的,如果不是这样,它将破坏CompletableFuture类的异步计算的契约。

正如我所说,这两个注释在同一方法上不能一起工作。如果你考虑一下,很明显。使用@Async标记@Cacheable方法也意味着将整个缓存管理委派给不同的异步线程。如果计算CompletableFuture的值需要很长时间才能完成,那么缓存中的值将在那之后被Spring Proxy放置。

显然,有一种解决方法。解决方法使用CompletableFuture promises 这一事实。我们来看看下面的代码。

@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所指的内容的详细描述。

希望它有所帮助。 欢呼声。

答案 1 :(得分:2)

根据SPR-12967,不支持ListenableFutureCompletableFuture)。

答案 2 :(得分:0)

从理论上讲,只要

,它就会起作用
  • @Cacheable后面的CacheManager实现不是序列化缓存对象(如Hazelcast支持的缓存)

  • 由于CompletableFuture拥有一个状态,可以通过调用例如cancel()方法,重要的是API的所有用户都不会乱用缓存对象。否则,可能存在无法再检索Future内的缓存对象,并且需要缓存驱逐的风险

  • 值得验证注释背后代理的调用顺序。即@Cacheable代理是否始终在@Async之前调用?或者相反?还是取决于?例如,如果之前调用@Async,则会在Callable内触发ForkJoinPool,然后从缓存中检索其他对象。

答案 3 :(得分:0)

在一个类中的方法上添加@Async注释,在另一个类中的方法级别上添加@Cacheable注释。

然后从服务或任何其他层调用@Async方法。

Redis缓存和Async都对我有用,大大提高了性能。

答案 4 :(得分:0)

我尝试了以下方法,它似乎有效。

  • 使用 @Cachable 创建一个执行实际业务逻辑的方法
  • @Async 创建一个方法,它调用上面的 @Cachable 方法并返回一个 CompletableFuture
  • 在主执行流程中使用 @Async 调用方法

示例:

public class Main {
    public void cachedAsyncData() {
        try {
            asyncFetcher.getData().get();
        } catch(Exception e){}
    }
}

public class AsyncFetcher {
    @Async
    public CompletableFuture<String> getData() {
        return CompletableFuture.completedFuture(cacheFetcher.getData());
    }
}

public class CacheFetcher {
    @Cacheable
    public String getData() {
        return "DATA";
    }
}