繁体   English   中英

Spring @Cacheable 和@Async 注解

[英]Spring @Cacheable and @Async annotation

我需要缓存一些异步计算的结果。 具体来说,为了克服这个问题,我正在尝试使用 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 object 本身不感兴趣,但对代表计算的CompletableFuture感兴趣。

社区要求我做一些实验,所以我做了它们。 我发现我的问题的答案很简单:如果@Cacheable@Async放在同一个方法之上,它们就不能一起工作。

需要明确的是,我并不是在寻求一种方法来直接让缓存返回CompletableFuture拥有的对象。 这是不可能的,如果不是这样,就会破坏CompletableFuture类的异步计算契约。

正如我所说,这两个注释不能在同一个方法上一起工作。 如果你仔细想想,那就很明显了。 @Async标记也是@Cacheable手段,将整个缓存管理委托给不同的异步线程。 如果CompletableFuture的值的计算需要很长时间才能完成,那么 Spring Proxy 将在该时间之后放置缓存中的值。

显然,有一个解决方法。 解决方法使用CompletableFuturepromise的事实。 让我们看看下面的代码。

@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));
    }
}

诀窍是创建一个不完整的承诺并立即从标有@Cacheable注释的方法中返回它。 承诺将由另一个拥有用@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所指内容的详细描述。

我希望它有帮助。 干杯。

根据SPR- ListenableFutureListenableFuture ( CompletableFuture ) 不受支持。

添加@Async在一类和方法的注释@Cacheable在方法级别批注在不同的类。

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

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

理论上,只要

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

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

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

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

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

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

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

希望能帮到你!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM