简体   繁体   English

如何在 Spring Boot 中缓存 CompletableFuture 的值

[英]How to cache the value of a CompletableFuture in Spring Boot

I am trying to play around with CompletableFuture and Async in Spring Boot.我正在尝试在 Spring Boot 中使用CompletableFutureAsync

I have a service which basically returns a String object.我有一个服务,它基本上返回一个String对象。 The method of the service has @Cacheable on it, which makes it serve from the Redis cache if it's available.服务的方法上有@Cacheable ,这使得它可以从Redis 缓存中提供服务(如果可用)。

I have the following code for that:我有以下代码:

@Override
@Cacheable(value = "url-single", key = "#shortUrlKey", unless = "#result == null")
public Optional<String> retrieveOriginalUrl(String shortUrlKey) {
        LOG.info("Finding original URL with Short URL Key: {}", shortUrlKey);
        Optional<URLEntity> urlEntityOptional = urlRepository.findOneByShortUrlKey(shortUrlKey);
        if (urlEntityOptional.isPresent()) {
            LOG.info("Retrieved URL Entity: {}", urlEntityOptional.get());
            return Optional.of(urlEntityOptional.get().getOriginalUrl());
        }
        LOG.info("No URL Entity Retrieved");
        return Optional.empty();
}

The code above works fine when there's no CompletableFuture involved.当不涉及CompletableFuture时,上面的代码工作正常。

But the it crashes when the method is modified as below:但是当方法修改如下时它崩溃了:

@Async
@Override
@Cacheable(value = "url-single", key = "#shortUrlKey", unless = "#result == null")
public CompletableFuture<Optional<String>> retrieveOriginalUrl(String shortUrlKey) {
        LOG.info("Finding original URL with Short URL Key: {}", shortUrlKey);
        Optional<URLEntity> urlEntityOptional = urlRepository.findOneByShortUrlKey(shortUrlKey);
        if (urlEntityOptional.isPresent()) {
            LOG.info("Retrieved URL Entity: {}", urlEntityOptional.get());
            return CompletableFuture.completedFuture(Optional.of(urlEntityOptional.get().getOriginalUrl()));
        }
        LOG.info("No URL Entity Retrieved");
        return CompletableFuture.completedFuture(Optional.empty());
    }

I use Redisson for caching.我使用Redisson进行缓存。 It throws the following error when the particular method is called调用特定方法时会引发以下错误

java.lang.IllegalArgumentException: java.io.IOException: java.lang.RuntimeException: Class java.util.concurrent.CompletableFuture does not implement Serializable or externalizable
    at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:326) ~[redisson-3.12.1.jar!/:3.12.1]
    at org.redisson.RedissonMap.fastPutOperationAsync(RedissonMap.java:931) ~[redisson-3.12.1.jar!/:3.12.1]
    at org.redisson.RedissonMap.fastPutAsync(RedissonMap.java:922) ~[redisson-3.12.1.jar!/:3.12.1]
    at org.redisson.RedissonMap.fastPut(RedissonMap.java:936) ~[redisson-3.12.1.jar!/:3.12.1]
    at org.redisson.spring.cache.RedissonCache.put(RedissonCache.java:109) ~[redisson-3.12.1.jar!/:3.12.1]
    at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:87) ~[spring-context-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.cache.interceptor.CacheAspectSupport$CachePutRequest.apply(CacheAspectSupport.java:820) ~[spring-context-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:429) ~[spring-context-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345) ~[spring-context-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61) ~[spring-context-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at com.sun.proxy.$Proxy92.retrieveOriginalUrlAsync(Unknown Source) ~[na:na]
    at in.turls.lib.controllers.v1.TestAsync.redirect(TestAsync.java:75) ~[classes!/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at in.turls.lib.filters.RequestResponseLoggingFilter.doFilter(RequestResponseLoggingFilter.java:28) ~[classes!/:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.29.jar!/:9.0.29]
    at java.base/java.lang.Thread.run(Thread.java:830) ~[na:na]
Caused by: java.io.IOException: java.lang.RuntimeException: Class java.util.concurrent.CompletableFuture does not implement Serializable or externalizable
    at org.redisson.codec.FstCodec$2.encode(FstCodec.java:279) ~[redisson-3.12.1.jar!/:3.12.1]
    at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:324) ~[redisson-3.12.1.jar!/:3.12.1]
    ... 66 common frames omitted
Caused by: java.lang.RuntimeException: Class java.util.concurrent.CompletableFuture does not implement Serializable or externalizable
    at org.nustaq.serialization.FSTClazzInfo.<init>(FSTClazzInfo.java:144) ~[fst-2.57.jar!/:na]
    at org.nustaq.serialization.FSTClazzInfoRegistry.getCLInfo(FSTClazzInfoRegistry.java:129) ~[fst-2.57.jar!/:na]
    at org.nustaq.serialization.FSTObjectOutput.getFstClazzInfo(FSTObjectOutput.java:534) ~[fst-2.57.jar!/:na]
    at org.nustaq.serialization.FSTObjectOutput.writeObjectWithContext(FSTObjectOutput.java:416) ~[fst-2.57.jar!/:na]
    at org.nustaq.serialization.FSTObjectOutput.writeObjectInternal(FSTObjectOutput.java:327) ~[fst-2.57.jar!/:na]
    at org.nustaq.serialization.FSTObjectOutput.writeObject(FSTObjectOutput.java:294) ~[fst-2.57.jar!/:na]
    at org.nustaq.serialization.FSTObjectOutput.writeObject(FSTObjectOutput.java:204) ~[fst-2.57.jar!/:na]
    at org.redisson.codec.FstCodec$2.encode(FstCodec.java:271) ~[redisson-3.12.1.jar!/:3.12.1]
    ... 67 common frames omitted

When I use Optional , Spring cache is able to store just the value of it, but when using CompletableFuture , it fails to do so.当我使用Optional ,Spring 缓存能够只存储它的值,但是当使用CompletableFuture ,它无法这样做。 Considering the fact that the method must return a CompletableFuture only, what are my options here?考虑到该方法必须只返回CompletableFuture的事实,我在这里有什么选择?

This:这个:

Class java.util.concurrent.CompletableFuture does not implement Serializable or externalizable

is telling you that Spring cache is trying to serialize the actual Future object, instead of its promised value.告诉您 Spring 缓存正在尝试序列化实际的 Future 对象,而不是其承诺的值。

As per Spring team : CompletableFuture is not ( and will not be ) supported by Spring cache.根据 Spring 团队:Spring 缓存支持(也不会)支持 CompletableFuture。

it sounds it's part of a more general reactive effort.听起来这是更普遍的反应性努力的一部分。

You could switch to using project reactor.您可以切换到使用项目反应器。 But Mono/Flux come with their own hurdles.但是 Mono/Flux 也有自己的障碍。 The other option is directly using the cache API, like Caffeine cache API , or Redisson in your case.另一种选择是直接使用缓存 API,例如Caffeine cache APIRedisson

See this question: Spring @Cacheable and @Async annotation看到这个问题: Spring @Cacheable and @Async annotation

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

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