4 votes

Comment mettre en cache la valeur d'un CompletableFuture dans Spring Boot

J'essaie de jouer avec CompletableFuture y Async dans Spring Boot.

J'ai un service qui renvoie essentiellement un String objet. La méthode du service a @Cacheable sur lui, ce qui le fait servir à partir du cache Redis s'il est disponible.

J'ai le code suivant pour cela :

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

Le code ci-dessus fonctionne bien lorsqu'il n'y a pas de CompletableFuture impliqué.

Mais il se bloque lorsque la méthode est modifiée comme ci-dessous :

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

J'utilise Redisson pour la mise en cache. L'erreur suivante se produit lorsque la méthode en question est appelée

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

Quand j'utilise Optional le cache de Spring est capable de stocker uniquement sa valeur, mais lorsque l'on utilise l'option CompletableFuture il ne le fait pas. Considérant le fait que la méthode doit retourner un CompletableFuture seulement, quelles sont mes options ici ?

0voto

MMJ Points 18

Ceci :

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

vous indique que le cache de Spring essaie de sérialiser l'objet Future actuel, au lieu de sa valeur promise.

Selon l'équipe de printemps : CompletableFuture n'est pas ( et ne sera pas ) pris en charge par le cache de Spring.

il semble que cela fasse partie d'un effort réactif plus général.

Vous pourriez passer à l'utilisation du réacteur de projet. Mais Mono/Flux comportent leurs propres obstacles. L'autre option est d'utiliser directement l'API de cache, comme API de cache Caffeine ou Redisson dans votre cas.

Voir cette question : Annotation Spring @Cacheable et @Async

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X