The following code uses the function [sProxy] to intercept suspend functions.
It prints:
add[1, 2] called
3
typealias SInvocation = suspend () -> Any?
typealias SInterceptor =
suspend (method: Method, args: List<Any?>, invocation: SInvocation) -> Any?
interface Calculator {
suspend fun add(a: Int, b: Int): Int
}
class CalculatorImpl : Calculator {
override suspend fun add(a: Int, b: Int) = a + b
}
val Printer: SInterceptor = { method, args, invocation ->
println("${method.name}$args called")
invocation()
}
fun main() = runBlocking {
val calculator = sProxy(Calculator::class.java, CalculatorImpl(), Printer)
println(calculator.add(1, 2))
}
The following code shows the working implementation of the function [sProxy]:
interface SFunction {
suspend fun invoke(): Any?
}
val SRemover: Method = SFunction::class.java.methods[0]
typealias SInvoker = suspend (method: Method, args: List<Any?>) -> Any?
fun Method.sInvoke(
args: List<Any?>, continuation: Continuation<*>, invoker: SInvoker
): Any? = SRemover.invoke(object : SFunction {
override suspend fun invoke(): Any? = invoker(this@sInvoke, args)
}, continuation)
suspend fun Method.sInvoke(implementation: Any, args: List<Any?>): Any? =
suspendCoroutineUninterceptedOrReturn { continuation ->
sInvoke(args, continuation) { _, _ ->
invoke(implementation, *args.toTypedArray(), continuation)
}
}
/** Creates a proxy for [sContract] that intercepts calls to [implementation]. */
@Suppress("UNCHECKED_CAST")
fun <C : Any> sProxy(
sContract: Class<C>, implementation: C, interceptor: SInterceptor
): C = Proxy.newProxyInstance(
sContract.classLoader, arrayOf(sContract)
) { _, method, args ->
method.sInvoke(
args.take(args.size - 1), args.last() as Continuation<*>
) { _, params ->
interceptor(method, params) { method.sInvoke(implementation, params) }
}
} as C
So now the questions are:
- Is this a good/problematic solution?
- Is there a better solution?
Feedback would be highly appreciated,
maybe even from a member of the Kotlin core team?
You can find the full code at https://github.com/softappeal/yass/blob/master/kotlin/yass/test/ch/softappeal/yass/demo/DynamicProxyDemo.kt
I use it in my service framework for implementing non-blocking calls with Ktor.