kotlin-jvm-blocking-bridge

Call Kotlin suspend functions from Java with minimal effort

APACHE-2.0 License

Stars
85

kotlin-jvm-blocking-bridge

ENGLISH 英文

为 Kotlin suspend 函数快速生成‘阻塞式方法桥’的 Kotlin 编译器插件。

截图

Kotlin 挂起函数: image_2.png

阻塞式方法桥调用: image.png

文档和跳转支持: image_1.png

动机

Kotlin suspend 函数编译后会被加上一个额外参数 $completion: Continuation。在 Java 调用这样的方法很困难,我们可以做一些兼容:

suspend fun downloadImage(): Image

我们可以添加一个‘阻塞式方法桥’:

@JvmName("downloadImage") // 避免引用歧义
fun downloadImageBlocking(): Image = runBlocking { downloadImage() }

这样,Java 使用者可以直接通过 downloadImage() 调用,就像在 Kotlin 调用 suspend 函数。即使这损失了一些性能,但通常我们不在乎它。

然而,这也带来了一些问题:

  • KDoc 需要从原函数复制到额外添加的函数,并且修改时要同时修改两者
  • 修改函数签名(参数,修饰符,返回值,注解)变得十分不便
  • 我们不希望将 downloadImageBlocking 暴露给 Kotlin 调用方,但没有方法隐藏它们
    一个不太好的解决方法是添加 RequiresOptIn
    @RequiresOptIn(level = ERROR)
    annotation class JavaFriendlyApi
    
    @JavaFriendlyApi // 于是当 Kotlin 调用者调用这个函数时,IDE 会报告 ERROR 级别的错误。尽管 Kotlin 仍然能看到这些方法。
    @JvmName("downloadImage") // 避免引用歧义
    fun downloadImageBlocking(): Image = runBlocking { downloadImage() }
    
    但这似乎不是最好的----我们要重复 JavaFriendlyApi 这样的注解很多次,还需要到处使用它。

本编译器插件设计为最小化这样兼容 Java 时所作的额外工作----仅需添加一个注解:

@JvmBlockingBridge
suspend fun downloadImage(): Image

编译器会帮助生成上述的‘阻塞式方法桥’ fun downloadImage(),使用‘相同’的方法签名(编译级的生成不会引起调用歧义),且更高效。

Examples Of Usages

  1. 提供在 Java 调用 suspend 函数的最简单方式:

    interface Image
    
    object ImageManager {
        @JvmStatic
        @JvmBlockingBridge
        suspend fun getImage(): Image
    }
    
    class Test {
        public static void main(String[] args){
            Image image = ImageManager.getImage(); // just like in Kotlin, no need to implement Continuation.
        }
    }
    
  2. 在测试中,使用 @JvmBlockingBridge 来运行 suspend 的测试函数而不需要 runBlocking

    @file:JvmBlockingBridge
    
    class SomeTests {
        @Test
        suspend fun test() { /* ... */ }
    }
    

稳定性

编译器插件有超过 150 个单元测试来确保每一项功能的正常运行。

拥有 10 万行 Kotlin 代码的库 mirai 大量地在各种情况下使用了这个编译器插件。mirai 拥有严格二进制兼容测试,正在被成千上万的用户使用。 这意味着 Kotlin Jvm Blocking Bridge 提供稳定的编译结果,而且适用于生产环境。

使用要求

  • Gradle(仅在 6.0+ 环境通过测试)
  • Kotlin 1.4.20 或更高
  • IntelliJ IDEA 或 Android Studio(推荐保持新稳定版本)

现在体验

使用者

如果一个库使用了 Kotlin JVM Blocking Bridge,依赖方无需特别在意,可以就像普通库一样添加编译依赖使用。 即依赖使用或不使用 KJBB 编译的库的流程都是一样的。

库和应用程序作者

如果你正开发一个库或应用程序,你需要安装 Gradle 插件来获取编译支持,和安装 IntelliJ IDEA 插件来获取编辑支持。

安装 IntelliJ IDEA (或 Android Studio) 插件

本插件仅支持 IntelliJ IDEA 和 Android Studio。 Eclipse 和 Visual Studio 或其他 IDE 均不受支持。

一键安装:https://plugins.jetbrains.com/embeddable/install/14816,或者也可以手动安装:

  1. 打开 File->Settings->Plugins->Marketplace
  2. 搜索 Kotlin Jvm Blocking Bridge,下载并安装
  3. 重启 IDE

安装 Gradle 插件.

build.gradlebuild.gradle.kts 中添加:

plugins {
    id("me.him188.kotlin-jvm-blocking-bridge") version "VERSION"
}

可在 releases 获取 VERSION, 如 3.1.0-180.1.

You also need to add runtime dependency as follows. Please make sure you have it in runtime (use implementation or api, don't use compileOnly), because the compiled bridges needs the runtime library.

也许添加如下的依赖库。 请保证运行环境包含该库 (使用 implementationapi, 不要使用 compileOnly),因为编译的方法桥需要此依赖库。

implementation("me.him188:kotlin-jvm-blocking-bridge-runtime:VERSION")

对于 JVM 项目:

dependencies {
    implementation("me.him188:kotlin-jvm-blocking-bridge-runtime:VERSION")
}

对于多平台项目, 为 commonMain 添加即可将依赖添加到所有编译目标:

kotlin.sourceSets {
    commonMain {
        dependencies {
            implementation("me.him188:kotlin-jvm-blocking-bridge-runtime:VERSION")
        }
    }
}

如果 Gradle 无法下载这个插件,请在 settings.gradlesettings.gradle.kts 中添加 gradlePluginPortal():

pluginManagement {
    repositories {
        gradlePluginPortal()
    }
}

支持的编译器后端

Kotlin 拥有两个编译器后端,旧 JVM 和新 IR(Internal Representation)。 Kotlin 在 1.5 及以前使用 JVM 后端,在 1.6 及以后使用 IR 后端。

本插件同时支持这两个后端。在两个后端产生的编译结果都是相同的。

K2 暂未支持。

模块

如果你感兴趣于这个项目的原理,本章节可能会有帮助。当然你也可以直接使用插件了。

  • 运行时库 提供 @JvmBlockingBridge 注解
  • 编译器插件 提供编译代码生成,编译目标为 JVM 字节码或 Kotlin IR
  • ide-plugin

BridgeFeatures.md 阅读规范

运行时库

  • 提供 public annotation class me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
  • 提供 internal annotation class me.him188.kotlin.jvm.blocking.bridge.GeneratedBlockingBridge,由编译器插件自动添加到生成的方法桥上.
  • 提供编译后的阻塞式方法桥需要调用的一些库函数.

编译器插件

对于 Kotlin suspend 函数:

@JvmBlockingBridge
suspend fun test(a1: Int, a2: Any): String

本编译器插件生成与原函数具有‘相同’签名的‘阻塞式方法桥’ (仅 Java 可见)

@GeneratedBlockingBridge
fun test(a1: Int, a2: Any): String = `$runSuspend$` {
        test(a1, a2) // 调用原 suspend `test`  
    } // `$runSuspend$` 是一个运行时库中的函数, 因此不依赖 kotlinx-coroutines-core.

IDE (IntelliJ) 插件

  • 让 Java 用户能引用阻塞式方法桥 (即使它们还没有生成)
  • 为 Kotlin 用户隐藏生成的阻塞式方法桥 (即使它们已经生成)
Package Rankings
Top 23.69% on Repo1.maven.org