Add Coroutines support for @Cacheable
This commit adds Coroutines support for `@Cacheable`. It also refines SimpleKeyGenerator to ignore Continuation parameters (Kotlin does not allow to have the same method signature with both suspending and non-suspending variants) and refines org.springframework.aop.framework.CoroutinesUtils.awaitSingleOrNull in order to wrap plain value to Mono. Closes gh-31412
This commit is contained in:
@@ -48,6 +48,7 @@ import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.expression.AnnotatedElementKey;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -85,6 +86,7 @@ import org.springframework.util.function.SupplierUtils;
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
@@ -1024,6 +1026,9 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).toFuture())));
|
||||
}
|
||||
}
|
||||
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
|
||||
return Mono.fromFuture(cache.retrieve(key, () -> ((Mono<?>) invokeOperation(invoker)).toFuture()));
|
||||
}
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,9 +19,15 @@ package org.springframework.cache.interceptor;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import kotlin.coroutines.Continuation;
|
||||
import kotlin.coroutines.CoroutineContext;
|
||||
import kotlinx.coroutines.Job;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.CoroutinesUtils;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -39,6 +45,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -51,6 +58,9 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc
|
||||
|
||||
CacheOperationInvoker aopAllianceInvoker = () -> {
|
||||
try {
|
||||
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
|
||||
return KotlinDelegate.invokeSuspendingFunction(method, invocation.getThis(), invocation.getArguments());
|
||||
}
|
||||
return invocation.proceed();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
@@ -68,4 +78,16 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||
*/
|
||||
private static class KotlinDelegate {
|
||||
|
||||
public static Publisher<?> invokeSuspendingFunction(Method method, Object target, Object... args) {
|
||||
Continuation<?> continuation = (Continuation<?>) args[args.length - 1];
|
||||
CoroutineContext coroutineContext = continuation.getContext().minusKey(Job.Key);
|
||||
return CoroutinesUtils.invokeSuspendingFunction(coroutineContext, method, target, args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,9 @@
|
||||
package org.springframework.cache.interceptor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.KotlinDetector;
|
||||
|
||||
/**
|
||||
* Simple key generator. Returns the parameter itself if a single non-null
|
||||
@@ -30,6 +33,7 @@ import java.lang.reflect.Method;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.0
|
||||
* @see SimpleKey
|
||||
* @see org.springframework.cache.annotation.CachingConfigurer
|
||||
@@ -38,7 +42,8 @@ public class SimpleKeyGenerator implements KeyGenerator {
|
||||
|
||||
@Override
|
||||
public Object generate(Object target, Method method, Object... params) {
|
||||
return generateKey(params);
|
||||
return generateKey((KotlinDetector.isSuspendingFunction(method) ?
|
||||
Arrays.copyOf(params, params.length - 1) : params));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.context.expression;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -37,6 +38,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
*/
|
||||
public class MethodBasedEvaluationContext extends StandardEvaluationContext {
|
||||
@@ -55,7 +57,8 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext {
|
||||
|
||||
super(rootObject);
|
||||
this.method = method;
|
||||
this.arguments = arguments;
|
||||
this.arguments = (KotlinDetector.isSuspendingFunction(method) ?
|
||||
Arrays.copyOf(arguments, arguments.length - 1) : arguments);
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user