Using Kotlin

Kotlin是一种非常流行的编程语言,而它以JVM为目标(也在其他环境中)。在过去的几年中,Kotlin 的流行度飙升,使其成为最流行的JVM 语言,当然除了 Java 之外。 Quarkus 提供对使用 Kotlin 的一流支持,正如本指南中所解释的那样。

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

注:如需了解 Gradle 项目设置,请参阅下方,若要进一步了解,请查阅 Gradle setup page中的指南。

Creating the Maven project

首先,我们需要一个新的 Kotlin 项目。这可以通过使用以下命令来完成:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

将 `kotlin`添加到扩展列表时,Maven 插件将生成一个配置正确的项目以配合 Kotlin 使用。更重要的是,`org.acme.ReactiveGreetingResource`类和所生成的测试实现了 Kotlin 源代码。在扩展列表中添加 `rest-jackson`会导致导入 Quarkus REST(以前称为 RESTEasy Reactive)和 Jackson 扩展。

以下列样式显示 ReactiveGreetingResource

ReactiveGreetingResource.kt
package org.acme

import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType

@Path("/hello")
class ReactiveGreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    fun hello() = "Hello from Quarkus REST"
}

Update code

为了显示更多关于 Kotlin 用法的实际范例,我们将添加一个简单的 data class,如下所示: Greeting

Greeting.kt
package org.acme.rest

data class Greeting(val message: String = "")

我们也以这样的方式更新 `ReactiveGreetingResource`类:

import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.core.MediaType

@Path("/hello")
class ReactiveGreetingResource {

    @GET
    fun hello() = Greeting("hello")
}

随着这些变动的落实,`/hello`端点将用一个 JSON 对象代替普通的 String 回复。

为使测试通过,我们也需要以这样的方式更新 ReactiveGreetingResourceTest

import org.hamcrest.Matchers.equalTo

@QuarkusTest
class ReactiveGreetingResourceTest {

    @Test
    fun testHelloEndpoint() {
        given()
          .`when`().get("/hello")
          .then()
             .statusCode(200)
             .body("message", equalTo("hello"))
    }

}

Kotlin version

Quarkus Kotlin 扩展已经声明对一些基础 Kotlin 库的依赖,例如 kotlin-stdlib-jdk8`和 `kotlin-reflect。这些依赖项的 Kotlin 版本是在 Quarkus BOM 中声明的,目前位于 ${kotlin.version}。因此建议为其他 Kotlin 库使用相同的 Kotlin 版本。当向另一个基础 Kotlin 库(例如 kotlin-test-junit5)添加依赖项时,你不需要指定版本,因为 Quarkus BOM 包含了 Kotlin BOM

话虽如此,你仍然需要指定要使用的 Kotlin 编译器的版本。这里再次建议使用 Quarkus 为 Kotlin 库所使用的相同版本。

在 Quarkus 应用程序中通常不建议使用其他 Kotlin 版本。但为实现该目的,你必须导入 Kotlin BOM *before*以导入 Quarkus BOM。

Important Maven configuration points

与当未选择 Kotlin 时的对应项相比,所生成的 `pom.xml`包含以下修改:

  • `quarkus-kotlin`工件被添加到依赖项中。该工件提供在实时加载模式下对 Kotlin 的支持(更多信息将在后面给出)

  • `kotlin-stdlib-jdk8`也被添加为依赖项。

  • 配置 Maven 的 sourceDirectory`和 `testSourceDirectory`构建属性以指向 Kotlin 来源(分别为 `src/main/kotlin`和 `src/test/kotlin

  • 以如下方式配置 kotlin-maven-plugin

pom.xml
<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>test-compile</id>
            <goals>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <compilerPlugins>
            <plugin>all-open</plugin> 1
        </compilerPlugins>

        <pluginOptions>
            <!-- Each annotation is placed on its own line -->
            <option>all-open:annotation=jakarta.ws.rs.Path</option>
        </pluginOptions>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>
1 启用 all-open 注释插件(见下文讨论)

值得注意的是使用 all-open Kotlin 编译器插件。为了了解为什么需要此插件,首先我们需要指出,默认情况下,从 Kotlin 编译器生成的所有类均标记为 final

但是,拥有 final 类与需要创建 Dynamic Proxies 的各种框架配合得并不好。

因此,all-open Kotlin 编译器插件允许我们配置编译器,以将拥有特定注释的类标记为 not 类。在上面的片段中,我们指定了用 jakarta.ws.rs.Path 注释的类不应该成为 final

例如,如果你的应用程序包含用 jakarta.enterprise.context.ApplicationScoped 注释的 Kotlin 类,那么也需要添加 <option>all-open:annotation=jakarta.enterprise.context.ApplicationScoped</option>。对于任何需要在运行时创建动态代理的类(如 JPA 实体类)也是如此。

未来版本的 Quarkus 将以一种不需要更改此配置的方式配置 Kotlin 编译器插件。

Important Gradle configuration points

类似于 Maven 配置,当使用 Gradle 时,在选择 Kotlin 时需要进行以下修改:

  • `quarkus-kotlin`工件被添加到依赖项中。该工件提供在实时加载模式下对 Kotlin 的支持(更多信息将在后面给出)

  • `kotlin-stdlib-jdk8`也被添加为依赖项。

  • 激活 Kotlin 插件,它隐式添加 sourceDirectorytestSourceDirectory 构建属性来指向 Kotlin 源(分别为 src/main/kotlinsrc/test/kotlin

  • 全开放 Kotlin 插件告诉编译器不要将那些带有突出显示的注释的类标记为 final(根据需要自定义)

  • 使用本机映像时,必须声明 http(或 https)协议

  • An example configuration follows:

plugins {
    id 'java'
    id 'io.quarkus'

    id "org.jetbrains.kotlin.jvm" version "{kotlin-version}" (1)
    id "org.jetbrains.kotlin.plugin.allopen" version "{kotlin-version}" (1)
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:{kotlin-version}'

   implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")

    implementation 'io.quarkus:quarkus-rest'
    implementation 'io.quarkus:quarkus-rest-jackson'
    implementation 'io.quarkus:quarkus-kotlin'

    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'
}

group = '...' // set your group
version = '1.0.0-SNAPSHOT'

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

allOpen { (2)
    annotation("jakarta.ws.rs.Path")
    annotation("jakarta.enterprise.context.ApplicationScoped")
    annotation("jakarta.persistence.Entity")
    annotation("io.quarkus.test.junit.QuarkusTest")
}

compileKotlin {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_11
    kotlinOptions.javaParameters = true
}

compileTestKotlin {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_11
}
1 需要指定 Kotlin 插件版本。
2 根据上述 Maven 指南所需的全开放配置

或者,如果你使用 Gradle Kotlin DSL:

plugins {
    kotlin("jvm") version "{kotlin-version}" (1)
    kotlin("plugin.allopen") version "{kotlin-version}"
    id("io.quarkus")
}

repositories {
    mavenLocal()
    mavenCentral()
}

val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project

group = "..."
version = "1.0.0-SNAPSHOT"


repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))

    implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))

    implementation("io.quarkus:quarkus-kotlin")
    implementation("io.quarkus:quarkus-rest")
    implementation("io.quarkus:quarkus-rest-jackson")

    testImplementation("io.quarkus:quarkus-junit5")
    testImplementation("io.rest-assured:rest-assured")
}

group = '...' // set your group
version = "1.0.0-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

allOpen { (2)
    annotation("jakarta.ws.rs.Path")
    annotation("jakarta.enterprise.context.ApplicationScoped")
    annotation("jakarta.persistence.Entity")
    annotation("io.quarkus.test.junit.QuarkusTest")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
    kotlinOptions.javaParameters = true
}
1 需要指定 Kotlin 插件版本。
2 根据上述 Maven 指南所需的全开放配置

Overriding the Quarkus BOM Kotlin version (Gradle)

如果你想在应用程序中使用不同于 Quarkus BOM 指定的版本(例如,尝试预发布功能或出于兼容性原因),你可以通过在 Gradle 依赖关系中使用 strictly {} 版本修改器来执行此操作。例如:

plugins {
    id("io.quarkus")
    kotlin("jvm") version "1.7.0-Beta"
    kotlin("plugin.allopen") version "1.7.0-Beta"
}

configurations.all {
    resolutionStrategy {
        force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0-Beta"
        force "org.jetbrains.kotlin:kotlin-reflect:1.7.0-Beta"
    }
}

Live reload

Quarkus 提供对源代码进行实时重新加载更改的支持。Kotlin 还可以使用此支持,这意味着开发人员可以更新其 Kotlin 源代码并立即看到其更改反映。

要了解此功能的操作,请首先执行:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

针对 http://localhost:8080/hello 执行 HTTP GET 请求时,你将看到一个 JSON 消息,其 message 字段值为 hello

现在,使用你喜欢的编辑器或 IDE,更新 ReactiveGreetingResource.kt`并将 `hello 方法更改为以下内容:

fun hello() = Greeting("hi")

现在,当你针对 http://localhost:8080/hello 执行 HTTP GET 请求时,你将看到一个 JSON 消息,其 message 字段值为 hi

需要注意的是,当更改相互依赖的 Java 和 Kotlin 源文件时,实时重新加载功能不可用。我们希望将来能解决此限制。

Configuring live reload compiler

如果需要在开发模式下自定义 kotlinc 使用的编译器标记,可以在 quarkus 插件中配置它们:

Maven
<plugin>
  <groupId>${quarkus.platform.group-id}</groupId>
  <artifactId>quarkus-maven-plugin</artifactId>
  <version>${quarkus.platform.version}</version>

  <configuration>
    <source>${maven.compiler.source}</source>
    <target>${maven.compiler.target}</target>
    <compilerOptions>
      <compiler>
        <name>kotlin</name>
        <args>
          <arg>-Werror</arg>
        </args>
      </compiler>
    </compilerOptions>
  </configuration>

  ...
</plugin>
Gradle (Groovy DSL)
quarkusDev {
    compilerOptions {
        compiler("kotlin").args(['-Werror'])
    }
}
Gradle (Kotlin DSL)
tasks.quarkusDev {
     compilerOptions {
        compiler("kotlin").args(["-Werror"])
    }
}

Packaging the application

和往常一样,可以使用以下命令打包应用程序:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

并使用 java -jar target/quarkus-app/quarkus-run.jar 执行。

您还可以使用以下命令构建本机可执行文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

Kotlin and Jackson

如果项目中添加了 com.fasterxml.jackson.module:jackson-module-kotlin 依赖项和 quarkus-jackson 扩展(或 quarkus-resteasy-jacksonquarkus-rest-jackson 扩展之一),则 Quarkus 会自动将 KotlinModule 注册到 ObjectMapper Bean(有关更多详细信息,请参阅 this 指南)。

当使用带有 native-image 的 Kotlin 数据类时,您可能会遇到序列化错误,尽管已注册了 Kotlin Jackson 模块,但使用 JVM 版本不会出现此错误。尤其是在具有更复杂的 JSON 层次结构时,较低节点上的问题会导致序列化失败。显示的错误消息是包罗万象的,通常会显示根对象的问题,但这不一定属实。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Address` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

为了确保与 native-image 完全兼容,建议应用 Jackson @field:JsonProperty("fieldName") 注释,并设置可为空的默认值,如下所示。您可以使用 IntelliJ IDEA 插件(例如 JSON 到 Kotlin 类)自动生成示例 JSON 的 Kotlin 数据类,并轻松启用 Jackson 注释并选择可为空的参数,作为自动代码生成的一部分。

import com.fasterxml.jackson.annotation.JsonProperty

data class Response(
	@field:JsonProperty("chart")
	val chart: ChartData? = null
)

data class ChartData(
	@field:JsonProperty("result")
	val result: List<ResultItem?>? = null,

	@field:JsonProperty("error")
	val error: Any? = null
)

data class ResultItem(
	@field:JsonProperty("meta")
	val meta: Meta? = null,

	@field:JsonProperty("indicators")
	val indicators: IndicatorItems? = null,

	@field:JsonProperty("timestamp")
	val timestamp: List<Int?>? = null
)

...

Kotlin and the Kubernetes Client

在使用 quarkus-kubernetes 扩展以及绑定 Kotlin 类到 CustomResource 定义(例如在构建运算符时)时,您需要了解底层的 Fabric8 Kubernetes 客户端使用它自己的静态 Jackson ObjectMapper s,可以像下面这样使用 KotlinModule 来配置它:

import io.fabric8.kubernetes.client.utils.Serialization
import com.fasterxml.jackson.module.kotlin.KotlinModule

...
val kotlinModule = KotlinModule.Builder().build()
Serialization.jsonMapper().registerModule(kotlinModule)
Serialization.yamlMapper().registerModule(kotlinModule)

Please test this carefully on compilation to native images and fallback to Java-compatible Jackson bindings if you experience problems.

Coroutines support

Extensions

以下扩展通过允许在方法签名中使用 Kotlin 的 suspend 关键字,为 Kotlin 协程提供支持。

Extension Comments

quarkus-rest

为 Jakarta REST 资源方法提供支持

quarkus-rest-client

为 REST 客户端接口方法提供支持

quarkus-messaging

为消息传递方法提供支持

quarkus-scheduler

为调度器方法提供支持

quarkus-smallrye-fault-tolerance

为基于声明的注释 API 提供支持

quarkus-vertx

@ConsumeEvent 方法提供支持

quarkus-websockets-next

为服务器端和客户端端点方法提供支持

Kotlin coroutines and Mutiny

Kotlin 协程提供了一个命令式编程模型,该模型实际上是异步反应式方式执行的。为了简化 Mutiny 和 Kotlin 之间的互操作性,请使用 io.smallrye.reactive:mutiny-kotlin 模块, here 中对此模块进行了说明。

CDI @Inject with Kotlin

Kotlin 反射注释处理与 Java 不同。当使用 CDI @Inject 时,您可能会遇到错误,例如:“kotlin.UninitializedPropertyAccessException:lateinit 属性 xxx 未初始化”

在以下示例中,可以通过调整注释轻松解决此问题,添加 @field:Default,以处理 Kotlin 反射注释定义上 @Target 的缺失。

import jakarta.inject.Inject
import jakarta.enterprise.inject.Default
import jakarta.enterprise.context.ApplicationScoped

import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType



@ApplicationScoped
class GreetingService {

    fun greeting(name: String): String {
        return "hello $name"
    }

}

@Path("/")
class ReactiveGreetingResource {

    @Inject
    @field: Default (1)
    lateinit var service: GreetingService

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/hello/{name}")
    fun greeting(name: String): String {
        return service.greeting(name)
    }

}
1 Kotlin 需要 @field:xxx 限定符,因为它在注释定义中没有 @Target。在此示例中添加 @field:xxx。@Default 用作限定符,明确指定使用默认 Bean。

或者,倾向于使用构造函数注入,它无需修改 Java 示例即可工作,提高可测试性,并且最符合 Kotlin 编程风格。

import jakarta.enterprise.context.ApplicationScoped

import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType

@ApplicationScoped
class GreetingService {
    fun greeting(name: String): String {
        return "hello $name"
    }
}

@Path("/")
class ReactiveGreetingResource(
    private val service: GreetingService
) {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/hello/{name}")
    fun greeting(name: String): String {
        return service.greeting(name)
    }

}