Jimmy Zhou
03/20/2023, 9:15 PMwebServerExchange
object).
My initial attempt is:
1. pass the context from reactor to coroutine using context capture, and later i can retrieve the context without needing the webServerExchange
object
2. define an customized annotation such as WithReactiveContext
3. add this annotation to my controller class so that all my suspending function will be wrapped inside an different coroutine context (instead of the default one)
My questions are:
1. do you guys think this is a legit approach given my context
2. if this approach sounds okay, how do I trigger the annotation method using reflection so that all the suspending functions in annotated class is wrapped within a different coroutine context
3. if this approach does not make sense whats an alternative solutionSzymon Jeziorski
03/21/2023, 9:11 AMsuspend
controller functions? Spring Boot Webflux and even latest releases of Spring Boot MVC have implementations of integration with Kotlin Coroutines utilizing this library underneath:
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/
When suspend fun
from REST controller is entered, Reactor's context is already injected into current coroutine context.
Also, take a note that if you create project template on <http://start.spring.io|start.spring.io>
having kotlin
as a language and Spring Reactive Web
as a dependency, it will automatically include org.jetbrains.kotlinx:kotlinx-coroutines-reactor
in dependencies as wellJimmy Zhou
03/21/2023, 10:22 PMasCoroutineContext
or withContext
.
for example:
class MyController(){
suspend fun controller1(){
val context = coroutineContext // context will be passed from reactor to this scope, and you can get the context simply from import `kotlin.coroutines.coroutineContext` or `Mono.deferContextual`
return withContext(context) {
....
}
}
}
But this approach requires me to change all the controllers in my code base, and i am trying to avoid that.
What i am trying to find out is if its possible in kotlin to do the following: declare an annotation`@ExecuteWithContext` such that the execution of the suspend function will be wrapped inside another coroutine context
@ExecuteWithContext
class MyController(){
suspend fun controller1(){
return withContext(context) {
....
}
}
}
Jimmy Zhou
03/22/2023, 12:05 AMSzymon Jeziorski
03/22/2023, 10:20 AMcoroutineContext
and passing it to withContext
is redundant since coroutineContext
is already current context, so withContext
can be omitted entirely. So your example:
suspend fun controller1(){
val context = coroutineContext
return withContext(context) {
[processing]
}
}
could be just simplified to
suspend fun controller1(){
[processing]
}
So I understand you correctly, and what you're trying to achieve is to have suspend
controller functions with ReactorContext
integration (same as if they were Java controller's functions returning Mono or Flux), then you should be able to just declare those functions as suspend without manually handling integration as it is being taken care of before your functions are invoked so that they are invoked already within the proper context.Szymon Jeziorski
03/22/2023, 10:43 AMJimmy Zhou
03/22/2023, 2:32 PMElement
as context.
For example, if i want to pass MDCContext as context from the kotlin library in the webfilter i will have to add something like this:
.contextWrite { ctx -> ctx.put(MDCContext.Key, MDCContext(mapOf("1" to "2")))}
but this will only pass the context as a key value
pair instead of a coroutine element.
So in this example I printed the context as
[Context4{ kotlinx.coroutines.slf4j.MDCContext$Key@79272fd3=kotlinx.coroutines.slf4j.MDCContext@4424764e}, kotlinx.coroutines.UndispatchedMarker@3238f827]
but what i really need is
[Context4{}, kotlinx.coroutines.slf4j.MDCContext@1d486104, kotlinx.coroutines.UndispatchedMarker@3238f827]
Jimmy Zhou
03/22/2023, 2:38 PMJimmy Zhou
03/23/2023, 8:54 PMSzymon Jeziorski
03/24/2023, 1:49 PMJimmy Zhou
03/24/2023, 10:49 PM"YOUR-CONTEXT"
as the key)
step 2: pass context to coroutine (this comes for free as you mentioned)
step 3: define annotation along with spring or java aop and do something like this
@Around(
"""
@within(YourAnnotation) &&
args(.., kotlin.coroutines.Continuation)
""")
fun executeFunction(joinPoint: ProceedingJoinPoint): Any? {
return runCoroutine(joinPoint.args.last() as Continuation<Any?>) {
withContext(coroutineContext.get(ReactorContext.Key)?.context?.get("YOUR-CONTEXT") as YourContext) {
suspendCoroutineUninterceptedOrReturn<Any?> {
joinPoint.proceed(
joinPoint.args.sliceArray(0 until joinPoint.args.size - 1) + it)
}
}
}
}
note: YourContext
has to be a coroutine context element for example MDCContext from kotlinx
if you want to do method level you can do @annotation(YourAnnotation)
step 4: annotate your class with @YourAnnotation
this way you dont need to change any code, all the methods inside the class will execute with the jointPoint function