大多数有经验的开发人员都熟悉使用动态代理。它们使我们能够在运行时实现接口,并决定如何在调用方法时动态执行方法。这对于围绕Decorator模式所描述的现有实现(尤其是来自第三方库)添加额外功能非常有用。
以下为未例代码
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)
}
}
}