Make coroutines with custom AOP aspects work with @Transactional

Previous to this change, the transactional aspect would supersed the
user-defined AspectJ aspect, shortcircuiting to calling the original
Kotlin suspending function.

This change simplifies the TransactionAspectSupport way of dealing with
transactional coroutines, thanks to the fact that lower level support
for AOP has been introduced in c8169e5c.

Closes gh-33095
This commit is contained in:
Simon Baslé
2024-07-09 14:13:54 +02:00
parent 3ccaefe38f
commit 1d890a8952
3 changed files with 62 additions and 58 deletions

View File

@@ -23,12 +23,8 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import io.vavr.control.Try;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.Job;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -36,7 +32,6 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.CoroutinesUtils;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.NamedThreadLocal;
@@ -355,10 +350,6 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
boolean hasSuspendingFlowReturnType = isSuspendingFunction &&
COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());
if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {
throw new IllegalStateException("Coroutines invocation not supported: " + method);
}
CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
Class<?> reactiveType =
@@ -371,11 +362,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
return new ReactiveTransactionSupport(adapter);
});
InvocationCallback callback = invocation;
if (corInv != null) {
callback = () -> KotlinDelegate.invokeSuspendingFunction(method, corInv);
}
return txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, rtm);
return txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, rtm);
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
@@ -829,22 +816,6 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
}
/**
* Coroutines-supporting extension of the callback interface.
*/
protected interface CoroutinesInvocationCallback extends InvocationCallback {
Object getTarget();
Object[] getArguments();
default Object getContinuation() {
Object[] args = getArguments();
return args[args.length - 1];
}
}
/**
* Internal holder class for a Throwable in a callback transaction model.
*/
@@ -891,18 +862,6 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
*/
private static class KotlinDelegate {
public static Publisher<?> invokeSuspendingFunction(Method method, CoroutinesInvocationCallback callback) {
CoroutineContext coroutineContext = ((Continuation<?>) callback.getContinuation()).getContext().minusKey(Job.Key);
return CoroutinesUtils.invokeSuspendingFunction(coroutineContext, method, callback.getTarget(), callback.getArguments());
}
}
/**
* Delegate for Reactor-based management of transactional methods with a

View File

@@ -116,21 +116,7 @@ public class TransactionInterceptor extends TransactionAspectSupport implements
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}