Skip to content

Commit 0c319a8

Browse files
committed
Merge branch '6.1.x'
2 parents a9efe10 + 1d890a8 commit 0c319a8

File tree

3 files changed

+62
-58
lines changed

3 files changed

+62
-58
lines changed

integration-tests/src/test/kotlin/org/springframework/aop/framework/autoproxy/AspectJAutoProxyInterceptorKotlinIntegrationTests.kt

+60-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import kotlinx.coroutines.delay
2020
import kotlinx.coroutines.runBlocking
2121
import org.aopalliance.intercept.MethodInterceptor
2222
import org.aopalliance.intercept.MethodInvocation
23+
import org.aspectj.lang.ProceedingJoinPoint
24+
import org.aspectj.lang.annotation.Around
25+
import org.aspectj.lang.annotation.Aspect
2326
import org.assertj.core.api.Assertions.assertThat
2427
import org.junit.jupiter.api.Test
2528
import org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.InterceptorConfig
@@ -28,10 +31,18 @@ import org.springframework.beans.factory.annotation.Autowired
2831
import org.springframework.context.annotation.Bean
2932
import org.springframework.context.annotation.Configuration
3033
import org.springframework.context.annotation.EnableAspectJAutoProxy
34+
import org.springframework.stereotype.Component
3135
import org.springframework.test.annotation.DirtiesContext
3236
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
37+
import org.springframework.transaction.annotation.EnableTransactionManagement
38+
import org.springframework.transaction.annotation.Transactional
39+
import org.springframework.transaction.testfixture.ReactiveCallCountingTransactionManager
3340
import reactor.core.publisher.Mono
3441
import java.lang.reflect.Method
42+
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
43+
import kotlin.annotation.AnnotationTarget.CLASS
44+
import kotlin.annotation.AnnotationTarget.FUNCTION
45+
import kotlin.annotation.AnnotationTarget.TYPE
3546

3647

3748
/**
@@ -43,7 +54,9 @@ import java.lang.reflect.Method
4354
class AspectJAutoProxyInterceptorKotlinIntegrationTests(
4455
@Autowired val echo: Echo,
4556
@Autowired val firstAdvisor: TestPointcutAdvisor,
46-
@Autowired val secondAdvisor: TestPointcutAdvisor) {
57+
@Autowired val secondAdvisor: TestPointcutAdvisor,
58+
@Autowired val countingAspect: CountingAspect,
59+
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
4760

4861
@Test
4962
fun `Multiple interceptors with regular function`() {
@@ -67,8 +80,22 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
6780
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
6881
}
6982

83+
@Test // gh-33095
84+
fun `Aspect and reactive transactional with suspending function`() {
85+
assertThat(countingAspect.counter).isZero()
86+
assertThat(reactiveTransactionManager.commits).isZero()
87+
val value = "Hello!"
88+
runBlocking {
89+
assertThat(echo.suspendingTransactionalEcho(value)).isEqualTo(value)
90+
}
91+
assertThat(countingAspect.counter).`as`("aspect applied").isOne()
92+
assertThat(reactiveTransactionManager.begun).isOne()
93+
assertThat(reactiveTransactionManager.commits).`as`("transactional applied").isOne()
94+
}
95+
7096
@Configuration
7197
@EnableAspectJAutoProxy
98+
@EnableTransactionManagement
7299
open class InterceptorConfig {
73100

74101
@Bean
@@ -77,6 +104,13 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
77104
@Bean
78105
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
79106

107+
@Bean
108+
open fun countingAspect() = CountingAspect()
109+
110+
@Bean
111+
open fun transactionManager(): ReactiveCallCountingTransactionManager {
112+
return ReactiveCallCountingTransactionManager()
113+
}
80114

81115
@Bean
82116
open fun echo(): Echo {
@@ -107,6 +141,24 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
107141
}
108142
}
109143

144+
@Target(CLASS, FUNCTION, ANNOTATION_CLASS, TYPE)
145+
@Retention(AnnotationRetention.RUNTIME)
146+
annotation class Counting()
147+
148+
@Aspect
149+
@Component
150+
class CountingAspect {
151+
152+
var counter: Long = 0
153+
154+
@Around("@annotation(org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.Counting)")
155+
fun logging(joinPoint: ProceedingJoinPoint): Any {
156+
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
157+
counter++
158+
}
159+
}
160+
}
161+
110162
open class Echo {
111163

112164
open fun echo(value: String): String {
@@ -118,6 +170,13 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
118170
return value
119171
}
120172

173+
@Transactional
174+
@Counting
175+
open suspend fun suspendingTransactionalEcho(value: String): String {
176+
delay(1)
177+
return value
178+
}
179+
121180
}
122181

123182
}

spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java

+1-42
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,8 @@
2323
import java.util.concurrent.Future;
2424

2525
import io.vavr.control.Try;
26-
import kotlin.coroutines.Continuation;
27-
import kotlin.coroutines.CoroutineContext;
28-
import kotlinx.coroutines.Job;
2926
import org.apache.commons.logging.Log;
3027
import org.apache.commons.logging.LogFactory;
31-
import org.reactivestreams.Publisher;
3228
import reactor.core.publisher.Flux;
3329
import reactor.core.publisher.Mono;
3430

@@ -37,7 +33,6 @@
3733
import org.springframework.beans.factory.InitializingBean;
3834
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3935
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
40-
import org.springframework.core.CoroutinesUtils;
4136
import org.springframework.core.KotlinDetector;
4237
import org.springframework.core.MethodParameter;
4338
import org.springframework.core.NamedThreadLocal;
@@ -356,10 +351,6 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targe
356351
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
357352
boolean hasSuspendingFlowReturnType = isSuspendingFunction &&
358353
COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());
359-
if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {
360-
throw new IllegalStateException("Coroutines invocation not supported: " + method);
361-
}
362-
CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);
363354

364355
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
365356
Class<?> reactiveType =
@@ -372,11 +363,7 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targe
372363
return new ReactiveTransactionSupport(adapter);
373364
});
374365

375-
InvocationCallback callback = invocation;
376-
if (corInv != null) {
377-
callback = () -> KotlinDelegate.invokeSuspendingFunction(method, corInv);
378-
}
379-
return txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, rtm);
366+
return txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, rtm);
380367
}
381368

382369
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
@@ -864,22 +851,6 @@ protected interface InvocationCallback {
864851
}
865852

866853

867-
/**
868-
* Coroutines-supporting extension of the callback interface.
869-
*/
870-
protected interface CoroutinesInvocationCallback extends InvocationCallback {
871-
872-
Object getTarget();
873-
874-
Object[] getArguments();
875-
876-
default Object getContinuation() {
877-
Object[] args = getArguments();
878-
return args[args.length - 1];
879-
}
880-
}
881-
882-
883854
/**
884855
* Internal holder class for a Throwable in a callback transaction model.
885856
*/
@@ -928,18 +899,6 @@ public static Object evaluateTryFailure(Object retVal, TransactionAttribute txAt
928899
}
929900
}
930901

931-
/**
932-
* Inner class to avoid a hard dependency on Kotlin at runtime.
933-
*/
934-
private static class KotlinDelegate {
935-
936-
public static Publisher<?> invokeSuspendingFunction(Method method, CoroutinesInvocationCallback callback) {
937-
CoroutineContext coroutineContext = ((Continuation<?>) callback.getContinuation()).getContext().minusKey(Job.Key);
938-
return CoroutinesUtils.invokeSuspendingFunction(coroutineContext, method, callback.getTarget(), callback.getArguments());
939-
}
940-
941-
}
942-
943902

944903
/**
945904
* Delegate for Reactor-based management of transactional methods with a

spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java

+1-15
Original file line numberDiff line numberDiff line change
@@ -116,21 +116,7 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
116116
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
117117

118118
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
119-
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
120-
@Override
121-
@Nullable
122-
public Object proceedWithInvocation() throws Throwable {
123-
return invocation.proceed();
124-
}
125-
@Override
126-
public Object getTarget() {
127-
return invocation.getThis();
128-
}
129-
@Override
130-
public Object[] getArguments() {
131-
return invocation.getArguments();
132-
}
133-
});
119+
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
134120
}
135121

136122

0 commit comments

Comments
 (0)