Skip to content

Instantly share code, notes, and snippets.

@pjanczyk
Last active October 23, 2024 05:28
Show Gist options
  • Save pjanczyk/5d958821bafd911a5996bc0b66788ea3 to your computer and use it in GitHub Desktop.
Save pjanczyk/5d958821bafd911a5996bc0b66788ea3 to your computer and use it in GitHub Desktop.
AOP w połączeniu z Kotlin Coroutines | https://vived.io/aop-w-polaczeniu-z-kotlin-coroutines/
package com.example
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.AfterReturning
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.slf4j.LoggerFactory.getLogger
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
import kotlin.coroutines.Continuation
import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
@Component
class Runner(
val foo: Foo,
val reactiveFoo: ReactiveFoo,
val coroutineFoo: CoroutineFoo,
) : CommandLineRunner {
override fun run(vararg args: String?) {
foo.add(2, 3)
reactiveFoo.add(2, 3).block()
runBlocking {
coroutineFoo.add(2, 3)
}
}
}
annotation class Logging
annotation class ReactiveLogging
annotation class CoroutineLogging
@Component
class Foo {
@Logging
fun add(a: Int, b: Int): Int = a + b
}
@Component
@Aspect
class LoggingAspect {
private val logger = getLogger(LoggingAspect::class.java)
@AfterReturning(
pointcut = "@annotation(Logging)",
returning = "result"
)
fun logResult(joinPoint: JoinPoint, result: Any?) {
logger.info("`${joinPoint.signature}` returned `${result}`")
}
}
@Component
class ReactiveFoo {
@ReactiveLogging
fun add(a: Int, b: Int): Mono<Int> = Mono.just(a + b)
}
@Component
@Aspect
class ReactiveLoggingAspect {
private val logger = getLogger(ReactiveLoggingAspect::class.java)
@Around(
"""
@annotation(ReactiveLogging) &&
execution(reactor.core.publisher.Mono *(..))
"""
)
fun logResult(joinPoint: ProceedingJoinPoint): Mono<Any> =
(joinPoint.proceed() as Mono<Any>)
.doOnNext { result ->
logger.info("`${joinPoint.signature}` returned `${result}`")
}
}
@Component
class CoroutineFoo {
@CoroutineLogging
suspend fun add(a: Int, b: Int): Int {
delay(100)
return a + b
}
}
val ProceedingJoinPoint.coroutineContinuation: Continuation<Any?>
get() = this.args.last() as Continuation<Any?>
val ProceedingJoinPoint.coroutineArgs: Array<Any?>
get() = 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)
}
fun ProceedingJoinPoint.runCoroutine(
block: suspend () -> Any?
): Any? =
block.startCoroutineUninterceptedOrReturn(this.coroutineContinuation)
@Component
@Aspect
class CoroutineLoggingAspect {
private val logger = getLogger(CoroutineLoggingAspect::class.java)
@Around(
"""
@annotation(CoroutineLogging) &&
args(.., kotlin.coroutines.Continuation)
"""
)
fun logResult(joinPoint: ProceedingJoinPoint): Any? =
joinPoint.runCoroutine {
val result = joinPoint.proceedCoroutine()
logger.info("`${joinPoint.signature}` returned `${result}`")
result
}
}
plugins {
id("org.springframework.boot") version "2.5.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.21"
kotlin("plugin.spring") version "1.5.21"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-aop")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
}
@akhilpratap1991
Copy link

akhilpratap1991 commented Aug 11, 2022

@pjanczyk I tried using your code but it got into an infinite loop. Possible to check if I did any mistake. Using this for my project.

I put the annotation around a method that does an API call. In first call, you can see the actual args passed and then an API call is also made.but after that its just an infinite loop of null as args being passed along with Continuation object which is not printed.

Got these logs in infinite loop from below code::

11 Aug 2022 15:20:46,712 U e57e1dd58fcd [GET /testAPI] [http-nio-8501-exec-1] CoroutineLoggingAspect INFO   -  CoroutineLogging::
11 Aug 2022 15:20:46,712 U e57e1dd58fcd [GET /testAPI] [http-nio-8501-exec-1] CoroutineLoggingAspect INFO   -  pCarg: TestRequest(type=Test, user=PUserDetails(mcId=null, deviceId=null, userId=null), attributes=PAttributes(os=null)
11 Aug 2022 15:20:46,712 U e57e1dd58fcd [GET /testAPI] [http-nio-8501-exec-1] CoroutineLoggingAspect INFO   -  pCarg: {}
11 Aug 2022 15:20:46,712 U e57e1dd58fcd [GET /testAPI] [http-nio-8501-exec-1] CoroutineLoggingAspect INFO   -  pCarg: TEST_API_AOP
11 Aug 2022 15:20:46,739    [OkHttp http://www.someAPI.com/...] okhttp3.OkHttpClient INFO   -  --> POST http://www.someAPI.com/external-call (39-byte body)
11 Aug 2022 15:20:46,890    [OkHttp http://www.someAPI.com/...] okhttp3.OkHttpClient INFO   -  <-- 404 Not Found http://someAPI.com/external-call (150ms, 174-by
te body)
11 Aug 2022 15:20:46,890    [OkHttp http://www.someAPI.com/...] CoroutineLoggingAspect INFO   -  CoroutineLogging::
11 Aug 2022 15:20:46,890    [OkHttp http://www.someAPI.com/...] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,890    [OkHttp http://www.someAPI.com/...] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,890    [OkHttp http://www.someAPI.com/...] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  CoroutineLogging::
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  CoroutineLogging::
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  pCarg: null
11 Aug 2022 15:20:46,909    [DefaultDispatcher-worker-1] CoroutineLoggingAspect INFO   -  pCarg: null
..... infiniteloop
.......
package utils

import com.commonutils.logger
import kotlin.coroutines.Continuation
import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut

annotation class CoroutineLogging

val ProceedingJoinPoint.coroutineContinuation: Continuation<Any?>
  get() = this.args.last() as Continuation<Any?>

val ProceedingJoinPoint.coroutineArgs: Array<Any?>
  get() = this.args.sliceArray(0 until this.args.size - 1)

suspend fun ProceedingJoinPoint.proceedCoroutine(
  args: Array<Any?> = this.coroutineArgs
): Any? =
  suspendCoroutineUninterceptedOrReturn { continuation ->
    args.forEach {
      println("pCarg: $it")
    }
    this.proceed(args + continuation)
  }

fun ProceedingJoinPoint.runCoroutine(
  block: suspend () -> Any?
): Any? =
  block.startCoroutineUninterceptedOrReturn(this.coroutineContinuation)

@Aspect
class CoroutineLoggingAspect {
  private val LOG by logger()

  @Pointcut("execution(@CoroutineLogging * *.*(..))")
  private fun logging() {
  }

  @Around(
    """
        logging() &&
        args(.., kotlin.coroutines.Continuation)
        """
  )
  fun logResult(joinPoint: ProceedingJoinPoint): Any? {
    return joinPoint.runCoroutine {
      println("CoroutineLogging:: ")
      val result = joinPoint.proceedCoroutine()
      LOG.info("CoroutineLogging:: `${joinPoint.signature}` returned `${result}`")
      println("CoroutineLogging:: `${joinPoint.signature}` returned `${result}`")
      joinPoint.args.forEach {
        println("arg: $it")
      }
      println("\n\n")
      return@runCoroutine result
    }
  }
}

@und3rs
Copy link

und3rs commented Mar 28, 2023

Thank you so much!!
It works for me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment