我可以调用Kotlin协程从Java反射和包装它在CompletableFuture?



我反思性地访问Kotlin类并试图调用协程,将其包装在CompletableFuture中。看看Spring中的一个例子,他们使用kotlinx-coroutines-reactor:

将这样的反射调用包装在Mono中。
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
Mono<Object> mono = MonoKt.mono(Dispatchers.getUnconfined(), (scope, continuation) ->
KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation));

我可以很容易地调用Mono#toFuture(),但我试图避免将依赖拖到反应器中。如果不经过Mono,我能达到同样的效果吗?

查看kotlinx-coroutines-jdk8,似乎存在与上述类似的东西,但我不知道如何使用它。也许:

CompletableFuture<Object> future = FutureKt.future(
GlobalScope.INSTANCE, Dispatchers.getUnconfined(), CoroutineStart.DEFAULT, (scope, continuation) ->
KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation));

但这只是一个盲目的猜测,因为我几乎没有Kotlin知识。CoroutineStart.DEFAULTGlobalScope.INSTANCE在这里还行吗?

顺便说一句,春天是如何摆脱通过CoroutineDispatcher(Dispatchers.getUnconfined())的CoroutineContext预期?我应该使用EmptyCoroutineContext代替吗?

首先,可挂起的代码不容易从Java中调用,这是设计的。我们可以这样做,但正如你已经注意到的,这并不简单,需要一些关于协程的内部知识。

通常,创建一个适应层来将可挂起的代码转换为Java可以理解的东西是一个好主意:使用future()或使用runBlocking()的CompletableFuture阻塞代码。在Kotlin本身中编写这个更容易,所以即使您从Java中使用Kotlin库,并且库不提供这种适配器,您也可以考虑在消费者端添加一个非常薄的Kotlin包装器来完成转换。

如果这是不可能的,我们仍然可以使用相同的future()实用程序从Java,你已经这样做了。这种方法有点尴尬,因为我们需要为所有可选参数提供值。

future()通常很容易使用。我们需要提供一个lambda,它将调用我们的可挂起函数,并传递我们在lambda中接收到的延续。就是这样。然而,有多个参数来控制这个过程,它们使事情变得复杂。此外,如果我们不想使用ReflectJvmMapping.getKotlinFunction(),KCallables.callSuspend()getSuspendedFunctionArgs(),我们也不必使用。同样,我们所需要做的就是调用lambda中的函数,我们可以"用Java的方式"来实现。

简单的例子:

object Database {
@JvmStatic
suspend fun loadUsername(userId: Int): String {
delay(5000)
return "User$userId"
}
}
// Note the method receives additional `Continuation` parameter - it is always the last param.
Method method = Database.class.getMethod("loadUsername", int.class, Continuation.class);
CompletableFuture<String> future = FutureKt.future(
GlobalScope.INSTANCE, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (scope, continuation) -> {
try {
// We invoke the method passing its own arguments and the continuation.
return method.invoke(null, 5, continuation);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
System.out.println("Launched, waiting...");
var result = future.get();
// Result: User5
System.out.println("Result: " + result);

我们不必在这里使用Java反射,我们可以直接调用Database.loadUsername(5, continuation),但是这个问题是关于反射的。

参数说明:

  • GlobalScope.INSTANCE-这与结构化并发的概念有关,使用GlobalScope意味着后台操作没有任何"所有者"。来控制它。在Java中,我们没有结构化并发,所以在大多数情况下,使用GlobalScope就可以了。
  • EmptyCoroutineContext.INSTANCE-我认为EmptyCoroutineContextDispatchers.getUnconfined()更安全。不同之处在于,非受限调度程序在启动或恢复它的线程内运行协程;在本例中,空上下文使用一个特殊的全局线程池。非受限的性能更好,但它是一个更高级的特性,只有在我们知道可挂起代码并确定使用非受限是安全的情况下,我们才应该使用它。对于通用的适应层来说,这听起来不太好。我不确定Spring团队决定使用unrestricted的原因是什么,也许他们有更具体的用例。

BTW,我也观察到Dispatchers.getUnconfined()不能用作协程上下文,但我无法解释为什么。看起来像是IntelliJ的某个bug。CoroutineDispatcher扩展AbstractCoroutineContextElement,实现CoroutineContext.Element扩展CoroutineContext。因此,CoroutineDispatcherCoroutineContext的子类型。事实上,即使IntelliJ说这是错误的,我们也可以编译这段代码并运行得很好。

最新更新