![](/img/trans.png)
[英]How can I reflectively call a method on a Scala object from Java?
[英]Can I invoke a Kotlin coroutine from Java reflectively and wrap it in CompletableFuture?
我正在反射性地訪問 Kotlin class 並嘗試調用協程,將其包裝在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()
,但我試圖避免拖入對 Reactor 的依賴。 我可以在不通過Mono
的情況下實現同樣的目標嗎?
查看kotlinx-coroutines-jdk8 ,似乎存在與上述類似的東西,但我不知道如何使用它。 也許:
CompletableFuture<Object> future = FutureKt.future(
GlobalScope.INSTANCE, Dispatchers.getUnconfined(), CoroutineStart.DEFAULT, (scope, continuation) ->
KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation));
但這只是盲目猜測,因為我有 next to 0 Kotlin 的知識。 CoroutineStart.DEFAULT
或GlobalScope.INSTANCE
在這里還可以嗎?
順便說一句, CoroutineContext
是如何在需要 CoroutineContext 的地方傳遞CoroutineDispatcher
( Dispatchers.getUnconfined()
) 的? 我應該改用EmptyCoroutineContext
嗎?
首先,可掛起代碼不容易從 Java 調用,這是設計使然。 我們可以做到,但正如您已經注意到的那樣,這並不簡單,需要一些有關協程的內部知識。
通常,創建一個適配層將可暫停代碼轉換為 Java 可以理解的內容是個好主意:使用future() 的CompletableFuture
或使用runBlocking()的阻塞代碼。 在 Kotlin 本身中編寫它更容易,因此即使您使用 Java 中的 Kotlin 庫並且該庫不為期貨提供此類適配器,您也可以考慮在消費者端添加一個非常薄的 Kotlin 包裝器來進行翻譯。
如果這不可能,我們仍然可以使用 Java 中的相同future()
實用程序,您已經這樣做了。 這種方法有點笨拙,因為我們需要為所有可選參數提供值。
future()
通常很容易使用。 我們需要提供一個 lambda,它將調用我們的可暫停 function,傳遞我們在 lambda 中收到的延續。就是這樣。 然而,有多個參數來控制過程,它們使事情復雜化。 此外,如果我們不想,我們不必使用ReflectJvmMapping.getKotlinFunction()
、 KCallables.callSuspend()
和getSuspendedFunctionArgs()
。 同樣,我們需要做的就是在 lambda 中調用 function,我們可以“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
- 我認為使用EmptyCoroutineContext
比使用Dispatchers.getUnconfined()
更安全。 不同之處在於,無限制調度程序在啟動或恢復它的線程內運行協程; 本例中的空上下文使用了一個特殊的全局線程池。 Unconfined 的性能更好,但它是一個更高級的功能,只有當我們知道可掛起的代碼並且可以確定使用 unconfined 是安全的時,我們才應該使用它。 對於通用適配層來說聽起來不太好。 我不確定 Spring 團隊決定使用 unconfined 的原因是什么,也許他們有更具體的用例。 順便說一句,我還觀察到Dispatchers.getUnconfined()
不能用作協程上下文,但我無法解釋原因。 似乎是 IntelliJ 中的某種錯誤(?)。 CoroutineDispatcher
擴展了AbstractCoroutineContextElement
實現CoroutineContext.Element
擴展CoroutineContext
。 因此, CoroutineDispatcher
是CoroutineContext
的子類型。 事實上,我們可以編譯這段代碼並且它運行得很好,即使 IntelliJ 說它是錯誤的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.