Как кэшировать значение CompletableFuture в Spring Boot - PullRequest
2 голосов
/ 24 февраля 2020

Я пытаюсь поиграться с CompletableFuture и Async в Spring Boot.

У меня есть служба, которая в основном возвращает объект String. Метод службы имеет @Cacheable, что делает его доступным из кэша Redis, если он доступен.

Для этого у меня есть следующий код:

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

Приведенный выше код работает нормально, если нет CompletableFuture участия.

Но происходит сбой, когда метод изменено как показано ниже:

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

Я использую Redisson для кэширования. Он выдает следующую ошибку, когда конкретный метод вызывается

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

Когда я использую Optional, Spring cache может хранить только его значение, но при использовании CompletableFuture он не может сделать так. Учитывая тот факт, что метод должен возвращать только CompletableFuture, каковы мои варианты здесь?

...