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:
@@ -20,6 +20,9 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.aopalliance.intercept.MethodInterceptor
|
||||
import org.aopalliance.intercept.MethodInvocation
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.InterceptorConfig
|
||||
@@ -28,10 +31,18 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.transaction.testfixture.ReactiveCallCountingTransactionManager
|
||||
import reactor.core.publisher.Mono
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
|
||||
import kotlin.annotation.AnnotationTarget.CLASS
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
import kotlin.annotation.AnnotationTarget.TYPE
|
||||
|
||||
|
||||
/**
|
||||
@@ -43,7 +54,9 @@ import java.lang.reflect.Method
|
||||
class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
@Autowired val echo: Echo,
|
||||
@Autowired val firstAdvisor: TestPointcutAdvisor,
|
||||
@Autowired val secondAdvisor: TestPointcutAdvisor) {
|
||||
@Autowired val secondAdvisor: TestPointcutAdvisor,
|
||||
@Autowired val countingAspect: CountingAspect,
|
||||
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
|
||||
|
||||
@Test
|
||||
fun `Multiple interceptors with regular function`() {
|
||||
@@ -67,8 +80,22 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
|
||||
}
|
||||
|
||||
@Test // gh-33095
|
||||
fun `Aspect and reactive transactional with suspending function`() {
|
||||
assertThat(countingAspect.counter).isZero()
|
||||
assertThat(reactiveTransactionManager.commits).isZero()
|
||||
val value = "Hello!"
|
||||
runBlocking {
|
||||
assertThat(echo.suspendingTransactionalEcho(value)).isEqualTo(value)
|
||||
}
|
||||
assertThat(countingAspect.counter).`as`("aspect applied").isOne()
|
||||
assertThat(reactiveTransactionManager.begun).isOne()
|
||||
assertThat(reactiveTransactionManager.commits).`as`("transactional applied").isOne()
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
@EnableTransactionManagement
|
||||
open class InterceptorConfig {
|
||||
|
||||
@Bean
|
||||
@@ -77,6 +104,13 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
@Bean
|
||||
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
|
||||
|
||||
@Bean
|
||||
open fun countingAspect() = CountingAspect()
|
||||
|
||||
@Bean
|
||||
open fun transactionManager(): ReactiveCallCountingTransactionManager {
|
||||
return ReactiveCallCountingTransactionManager()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun echo(): Echo {
|
||||
@@ -107,6 +141,24 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
}
|
||||
}
|
||||
|
||||
@Target(CLASS, FUNCTION, ANNOTATION_CLASS, TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Counting()
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
class CountingAspect {
|
||||
|
||||
var counter: Long = 0
|
||||
|
||||
@Around("@annotation(org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.Counting)")
|
||||
fun logging(joinPoint: ProceedingJoinPoint): Any {
|
||||
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
|
||||
counter++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class Echo {
|
||||
|
||||
open fun echo(value: String): String {
|
||||
@@ -118,6 +170,13 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
return value
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Counting
|
||||
open suspend fun suspendingTransactionalEcho(value: String): String {
|
||||
delay(1)
|
||||
return value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user