-
Notifications
You must be signed in to change notification settings - Fork 38.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spring coroutines AOP is not compatible with @Transactional
#33095
Comments
It is likely coming from the fact using @Around("@annotation(com.example.aopwithtransaction.aop.Logging)")
fun logging(joinPoint: ProceedingJoinPoint): Any? {
log.info { "Aop Logging started" }
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
log.info { "Aop Logging completed" }
}
} As a consequence, I am closing the issue as invalid. |
@sdeleuze @Around("@annotation(com.example.aopwithtransaction.aop.Logging)")
fun logging(joinPoint: ProceedingJoinPoint): Any? {
log.info { "Aop Logging started" }
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
log.info { "Aop Logging completed" }
}
} Even when using the code you provided, applying both @transactional and @logging annotations on a method results in only @transactional being applied, and the @logging AOP is not applied. The @logging AOP only works when @order is also added to the code you provided. Is this really an issue related to the use of Mono? The same issue occurs even when using coroutines. @Service
class TargetService() {
private val log = KotlinLogging.logger { }
@CoroutineLogging
@Transactional
suspend fun coroutineAop(): String {
delay(100)
log.info { "aop target method call" }
return "ok"
}
} @Aspect
//@Order(1)
@Component
class LoggingAspect {
private val log = KotlinLogging.logger {}
@Around("@annotation(com.example.aopwithtransaction.aop.CoroutineLogging)")
fun coroutineLogging(joinPoint: ProceedingJoinPoint): Any? {
return joinPoint.runCoroutine {
log.info { "coroutine logging" }
val result = joinPoint.proceedCoroutine().let { result ->
if (result is Mono<*>) {
result.awaitSingleOrNull()
} else {
result
}
}
log.info { "Coroutine logging" }
result
}
}
} fun ProceedingJoinPoint.runCoroutine(
block: suspend () -> Any?,
): Any? = block.startCoroutineUninterceptedOrReturn(this.coroutineContinuation())
@Suppress("UNCHECKED_CAST")
fun ProceedingJoinPoint.coroutineContinuation(): Continuation<Any?> {
return this.args.last() as Continuation<Any?>
}
fun ProceedingJoinPoint.coroutineArgs(): Array<Any?> {
return this.args.sliceArray(0 until this.args.size - 1)
}
suspend fun ProceedingJoinPoint.proceedCoroutine(
args: Array<Any?> = this.coroutineArgs(),
): Any? = suspendCoroutineUninterceptedOrReturn { continuation ->
this.proceed(args + continuation)
} |
I checked locally with: @GetMapping("/aop")
suspend fun aop(): String {
delay(100)
return targetService.aop().awaitSingle()
} And @Aspect
@Component
class LoggingAspect {
private val log = KotlinLogging.logger {}
@Around("@annotation(com.example.aopwithtransaction.aop.Logging)")
fun logging(joinPoint: ProceedingJoinPoint): Any? {
log.info { "Aop Logging started" }
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
log.info { "Aop Logging completed" }
}
}
} And the logging is working. |
@sdeleuze Since TargetService.aop() is a suspend function, awaitSingle is not possible. The reason AOP logging is printed in your local environment is probably because the targetService.aop() method did not have @transactional. When both @transactional and @logging are attached to targetService.aop() as shown below, only @transactional is applied, and AOP for logging is not applied. Please check the sample I provided again. @RestController
class AopController(
private val targetService: TargetService,
) {
@GetMapping("/aop")
suspend fun aop(): String {
return targetService.aop()
}
} @Service
class TargetService() {
private val log = KotlinLogging.logger { }
@Logging
@Transactional
suspend fun aop(): String {
delay(100)
log.info { "aop target method call" }
return "ok"
}
} @Aspect
@Component
class LoggingAspect() {
private val log = KotlinLogging.logger {}
@Around("@annotation(com.example.aopwithtransaction.aop.Logging)")
fun logging(joinPoint: ProceedingJoinPoint): Any? {
log.info { "Aop Logging started" }
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
log.info { "Aop Logging completed" }
}
}
}
result![]() |
Thanks for the updated sample, I can indeed reproduce (only with a suspending function, a function returning |
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 c8169e5. Closes spring-projectsgh-33095
@Transactional
When using Spring Coroutine AOP, if the @transactional annotation and a custom AOP are applied together without specifying an order in the Aspect, there is an issue where the custom AOP does not get applied.
In cases like the one above, the custom AOP does not function unless the order is specified, in which case it does function.
This is a simple example : https://github.com/backtony/spring-reactive-aop-transaction
The text was updated successfully, but these errors were encountered: