【问题标题】:Spring @Cacheable and @Async annotationSpring @Cacheable 和 @Async 注解
【发布时间】:2017-11-07 14:38:38
【问题描述】:

我需要缓存一些异步计算的结果。具体来说,为了解决这个问题,我尝试使用 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&lt;User&gt; 类型的对象吗?我知道Caffeine Cache 有类似的东西,但如果配置正确,我不明白 Spring 是否使用它。

编辑:我对User 对象本身不感兴趣,但对代表计算的CompletableFuture 感兴趣。

【问题讨论】:

  • 你试过了吗?
  • 这几天我得试试。 @Kayaman,我会让你知道我的测试结果。
  • 好。我一直在等着看有没有人在这里拿球。我自己做了一些简单的谷歌搜索,但只发现了@Async 中的FutureCompatibleFuture 的任务,这些任务已在4.2 中修复。
  • @Kayaman 我用我的结果发布了一个答案。希望对您有所帮助。

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


【解决方案1】:

社区要求我做一些实验,所以我做了。我发现我的问题的答案很简单:@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 所指的内容的详细描述。

我希望它有所帮助。 干杯。

【讨论】:

  • 我为你的尝试投了赞成票,但你的所作所为看起来像个黑客。放开Cacheable注解直接使用缓存管理器会更清楚。或者注册一个回调。
  • @Cacheable 注解的底层机制非常有用。我正在使用 Spring,所以我正在尝试按照它的理念做事。
【解决方案2】:

根据SPR-12967,不支持ListenableFuture (CompletableFuture)。

【讨论】:

  • 感谢您的回复。在您链接的 Jira 中,有人问了一个不同的问题。据我了解,在 Jira 中,用户要求缓存 ListenableFuture 的内部值。我觉得只要CompletableFuture履行Object的契约,就可以成功缓存,不是吗?
【解决方案3】:

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

然后从服务或任何不同的层调用@Async 方法。

Redis 缓存和异步都对我有用,极大地提高了性能。

【讨论】:

  • 嗨@Rohit,欢迎来到 Stack Overflow。据我了解,这类似于我们在项目中实施的解决方案。但是我认为添加一些代码来说明您的解决方案会很好,并且在描述中更加具体。
【解决方案4】:

理论上,它会一直工作

  • @Cacheable 后面的 CacheManager 实现未序列化缓存对象(如 Hazelcast 支持的缓存)

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

  • 值得验证注释后面的代理被调用的顺序。即@Cacheable 代理是否总是在@Async 之前调用?还是反过来?还是视情况而定?例如,如果之前调用了@Async,它将在ForkJoinPool 内触发Callable,然后从缓存中检索另一个对象。

【讨论】:

  • 不,它不起作用。今天我做了一些实验。明天我将发布这些实验的结果
【解决方案5】:

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

  • 使用@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";
    }
}

【讨论】:

    【解决方案6】:

    请在@Component 或@Serice 类级别添加注解@EnableAsync。 经验:

    @Service
    @Slf4j
    @EnableAsync //Add it to here
    public class CachingServiceImpl implements CachingService {
    

    希望能帮到你!

    【讨论】:

      猜你喜欢
      • 2020-02-29
      • 2012-03-31
      • 2013-11-25
      • 2021-08-22
      • 1970-01-01
      • 1970-01-01
      • 2019-03-29
      • 2017-05-22
      • 1970-01-01
      相关资源
      最近更新 更多