简体   繁体   中英

How to cache the value of a CompletableFuture in Spring Boot

I am trying to play around with CompletableFuture and Async in Spring Boot.

I have a service which basically returns a String object. The method of the service has @Cacheable on it, which makes it serve from the Redis cache if it's available.

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.

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. 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. Considering the fact that the method must return a CompletableFuture only, what are my options here?

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.

As per Spring team : CompletableFuture is not ( and will not be ) supported by Spring cache.

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. The other option is directly using the cache API, like Caffeine cache API , or Redisson in your case.

See this question: Spring @Cacheable and @Async annotation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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