Posted 码农乐园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Kotlin 协程创建动态代理相关的知识,希望对你有一定的参考价值。
够在运行时实现接口,并决定如何在调用方法时动态执行方法。这对于在装饰器模式中描述的现有实现(尤其是来自第三方库)周围添加附加功能非常有用。然而,Kotlin协程在创建动态代理时引入了一系列新问题。我们将探讨如何正确检测和动态调用在接口上声明的挂起函数。
设置
首先,使用 Gradle 创建一个简单的 Kotlin 控制台应用程序并添加以下依赖项。
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
ITest
interface ITest
fun test(): String?
@Throws(Exception::class)
fun testException(): String?
suspend fun testSuspend(): String?
@Throws(Exception::class)
suspend fun testExceptionSuspend(): String?
ITest
class TestImpl: ITest
override fun test(): String?
return "test result"
@Throws(Exception::class)
override fun testException(): String?
throw Exception("You called testException().")
override suspend fun testSuspend(): String?
delay(1000)
return "testSuspend result"
@Throws(Exception::class)
override suspend fun testExceptionSuspend(): String?
delay(1000)
throw Exception("You called testExceptionSuspend().")
目标
WrappedTestException
class WrappedTestException(cause: Throwable): Exception(cause)
缺乏经验的方法
InvocationTargetExceptionWrappedTestException
/**
* File: main.kt
*
* Dynamic proxy that naively catches and prints out exceptions and re-throws them wrapped as WrappedTestExceptions.
*/
class NaiveExceptionLogger<T>(private val instance: T): InvocationHandler
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any?
try
val nonNullArgs = args ?: arrayOf()
return method?.invoke(instance, *nonNullArgs)
catch(e: InvocationTargetException)
e.targetException?.let targetException ->
println("Naively caught underlying target exception $targetException")
throw WrappedTestException(targetException)
?: throw WrappedTestException(e)
TestFactorymainrunBlockingmain
object TestFactory
fun createNaively(): ITest
return Proxy.newProxyInstance(
ITest::class.java.classLoader,
arrayOf<Class<*>>(ITest::class.java),
NaiveExceptionLogger(TestImpl())) as ITest
fun main(args: Array<String>)
runBlocking
val test = TestFactory.createNaively()
println(test.test())
println(test.testSuspend())
try
test.testException()
throw IllegalStateException("Did not catch testException()")
catch(e: WrappedTestException)
try
test.testExceptionSuspend()
throw IllegalStateException("Did not catch testExceptionSuspend()")
catch(e: WrappedTestException)
当你运行它时,你应该得到以下输出:
test result
testSuspend result
Naively caught underlying target exception java.lang.Exception: You called testException().
Exception in thread "main" java.lang.Exception: You called testExceptionSuspend().
at TestImpl.testExceptionSuspend(main.kt:39)
at TestImpl$testExceptionSuspend$1.invokeSuspend(main.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:369)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:403)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:395)
at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:491)
at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:489)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at MainKt.main(main.kt:126)
Process finished with exit code 1
testExceptionSuspendNaiveExceptionLogger
正确的方法
testExceptionSuspend
resumeWithWrappedTestExceptionCoroutineScope
我们也可以根据自己的需求来组合这个上下文,例如通过 Dispatchers启动在IO 线程池中运行的调用。调用该方法时,我们现在只需要更改与我们的新的和改进的 Continuation 一起提供的最后一个参数。
既然我们已经在 IO 线程池中启动了方法调用,那么我们从动态调用的方法中返回什么?Kotlin 对挂起函数有一个神奇的返回值,它告诉运行时挂起执行并允许当前线程继续执行其他工作。它是 kotlin.coroutines.intrinsics。COROUTINE_SUSPENDED。
这是所有东西放在一起时的样子:
/**
* File: main.kt
*
* Dynamic proxy that correctly catches and prints out exceptions with proper handling for coroutines.
* Rethrows caught exceptions as WrappedTestException
*/
class CorrectExceptionLogger<T>(private val instance: T): InvocationHandler
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any?
val nonNullArgs = args ?: arrayOf()
try
val lastArg = nonNullArgs.lastOrNull()
if(lastArg == null || lastArg !is Continuation<*>)
// not a suspend func, just invoke regularly
return method?.invoke(instance, *nonNullArgs)
else
// Step 1: Wrap the underlying continuation to intercept exceptions.
@Suppress("UNCHECKED_CAST")
val originalContinuation = lastArg as Continuation<Any?>
val wrappedContinuation = object: Continuation<Any?>
override val context: CoroutineContext get() = originalContinuation.context
override fun resumeWith(result: Result<Any?>)
result.exceptionOrNull()?.let err ->
// Step 2: log intercepted exception and resume with our custom wrapped exception.
println("Correctly caught underlying coroutine exception $err")
originalContinuation.resumeWithException(WrappedTestException(err))
?: originalContinuation.resumeWith(result)
// Step 3: launch the suspend function with our wrapped continuation using the underlying scope and context, but force it to run in the IO thread pool
CoroutineScope(originalContinuation.context).launch(Dispatchers.IO + originalContinuation.context)
val argumentsWithoutContinuation = nonNullArgs.take(nonNullArgs.size - 1)
val newArgs = argumentsWithoutContinuation + wrappedContinuation
method?.invoke(instance, *newArgs.toTypedArray())
return kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
catch(e: InvocationTargetException)
e.targetException?.let targetException ->
println("Correctly caught underlying exception $targetException")
throw WrappedTestException(targetException)
?: throw WrappedTestException(e)
运行!
TestFactory
object TestFactory
fun createNaively(): ITest
return Proxy.newProxyInstance(
ITest::class.java.classLoader,
arrayOf<Class<*>>(ITest::class.java),
NaiveExceptionLogger(TestImpl())) as ITest
fun createCorrectly(): ITest
return Proxy.newProxyInstance(
ITest::class.java.classLoader,
arrayOf<Class<*>>(ITest::class.java),
CorrectExceptionLogger(TestImpl())) as ITest
fun main(args: Array<String>)
runBlocking
val test = TestFactory.createCorrectly()
println(test.test())
println(test.testSuspend())
try
test.testException()
throw IllegalStateException("Did not catch testException()")
catch(e: WrappedTestException)
try
test.testExceptionSuspend()
throw IllegalStateException("Did not catch testExceptionSuspend()")
catch(e: WrappedTestException)
您的输出应如下所示。如果是这样——恭喜!– 您现在已经创建了一个通用的动态代理,它可以与常规和挂起函数一起使用。
test result
testSuspend result
Correctly caught underlying exception java.lang.Exception: You called testException().
Correctly caught underlying coroutine exception java.lang.Exception: You called testExceptionSuspend().
Process finished with exit code 0以上是关于使用 Kotlin 协程创建动态代理的主要内容,如果未能解决你的问题,请参考以下文章