编辑此页面

使用 Kotlin

Kotlin 是一种非常流行的编程语言,它以 JVM(以及其他环境)为目标。Kotlin 在过去几年中经历了人气的飙升,使其成为最流行的 JVM 语言,当然 Java 除外。

Quarkus 提供了对使用 Kotlin 的一流支持,本指南将对此进行解释。

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

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

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

注意:对于 Gradle 项目设置,请参见下文,有关更多参考,请查阅 Gradle 设置页面中的指南。

创建 Maven 项目

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

CLI
quarkus create app org.acme:rest-kotlin-quickstart \
    --extension='kotlin,rest-jackson'
cd rest-kotlin-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-kotlin-quickstart \
    -Dextensions='kotlin,rest-jackson'
cd rest-kotlin-quickstart

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

对于 Windows 用户

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

  • 如果使用 Powershell,请将 -D 参数括在双引号中,例如 "-DprojectArtifactId=rest-kotlin-quickstart"

当将 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"
}

更新代码

为了展示 Kotlin 用法的更实际的示例,我们将添加一个简单的 数据类,名为 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 对象,而不是简单的字符串。

为了使测试通过,我们还需要更新 ReactiveGreetingResourceTest,如下所示

import org.hamcrest.Matchers.equalTo

@QuarkusTest
class ReactiveGreetingResourceTest {

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

}

Kotlin 版本

Quarkus Kotlin 扩展已经声明了对一些基本 Kotlin 库(如 kotlin-stdlib-jdk8kotlin-reflect)的依赖。这些依赖的 Kotlin 版本在 Quarkus BOM 中声明,目前为 2.1.21。因此,建议对其他 Kotlin 库使用相同的 Kotlin 版本。当向另一个基本 Kotlin 库(例如 kotlin-test-junit5)添加依赖时,您不需要指定版本,因为 Quarkus BOM 包括 Kotlin BOM

话虽如此,您仍然需要指定要使用的 Kotlin 编译器版本。同样,建议使用与 Quarkus 用于 Kotlin 库的版本相同的版本。

通常不建议在 Quarkus 应用程序中使用不同的 Kotlin 版本。但为了这样做,您必须在 Quarkus BOM 之前导入 Kotlin BOM。

重要的 Maven 配置点

与未选择 Kotlin 时的对应版本相比,生成的 pom.xml 包含以下修改

  • quarkus-kotlin 工件已添加到依赖项中。此工件为实时重新加载模式下的 Kotlin 提供支持(稍后将详细介绍)

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

  • Maven 的 sourceDirectorytestSourceDirectory 构建属性配置为指向 Kotlin 源代码(分别为 src/main/kotlinsrc/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 类对于需要创建 动态代理的各种框架来说效果不佳。

因此,all-open Kotlin 编译器插件允许我们配置编译器将具有某些注释的类标记为 final。在上面的代码片段中,我们指定用 jakarta.ws.rs.Path 注释的类不应是 final

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

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

重要的 Gradle 配置点

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

  • quarkus-kotlin 工件已添加到依赖项中。此工件为实时重新加载模式下的 Kotlin 提供支持(稍后将详细介绍)

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

  • Kotlin 插件已激活,这隐式地添加了 sourceDirectorytestSourceDirectory 构建属性,以指向 Kotlin 源代码(分别为 src/main/kotlinsrc/test/kotlin

  • all-open Kotlin 插件告诉编译器不要将带有突出显示的注释的类标记为 final(根据需要进行自定义)

  • 当使用 native-image 时,必须声明 http(或 https)协议的使用

  • 以下是一个示例配置

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

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

repositories {
    mavenLocal()
    mavenCentral()
}

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

   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_21
    targetCompatibility = JavaVersion.VERSION_21
}

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_21
    kotlinOptions.javaParameters = true
}

compileTestKotlin {
    kotlinOptions.jvmTarget = JavaVersion.VERSION_21
}
1 需要指定 Kotlin 插件版本。
2 所需的 all-open 配置,如上面的 Maven 指南所示

或者,如果您使用 Gradle Kotlin DSL

plugins {
    kotlin("jvm") version "2.1.21" (1)
    kotlin("plugin.allopen") version "2.1.21"
    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_21
    targetCompatibility = JavaVersion.VERSION_21
}

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

kotlin {
	compilerOptions {
		jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
		javaParameters = true
	}
}
1 需要指定 Kotlin 插件版本。
2 所需的 all-open 配置,如上面的 Maven 指南所示

覆盖 Quarkus BOM Kotlin 版本 (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"
    }
}

实时重新加载

Quarkus 提供了对实时重新加载源代码更改的支持。此支持也可用于 Kotlin,这意味着开发人员可以更新他们的 Kotlin 源代码并立即看到他们的更改反映出来。

要查看此功能的实际效果,请首先执行

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

当对 https://:8080/hello 执行 HTTP GET 请求时,您会看到一个 JSON 消息,其中 message 字段的值为 hello

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

fun hello() = Greeting("hi")

当您现在对 https://:8080/hello 执行 HTTP GET 请求时,您应该看到一个 JSON 消息,其中 message 字段的值为 hi

需要注意的一点是,当对彼此具有依赖关系的 Java 和 Kotlin 源代码进行更改时,实时重新加载功能不可用。我们希望在将来减轻此限制。

配置实时重新加载编译器

如果您需要自定义开发模式下 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"])
    }
}

打包应用程序

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

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 和 Jackson

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

当将 Kotlin 数据类与 native-image 一起使用时,您可能会遇到序列化错误,这些错误不会在 JVM 版本中发生,即使已注册 Kotlin Jackson 模块也是如此。如果您的 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 to Kotlin Class)自动生成示例 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 与 Quarkus REST 一起使用时,值得考虑使用 quarkus-rest-kotlin-serialization,它利用 Kotlin 序列化框架而不是 Jackson。

Kotlin 和 Kubernetes 客户端

当使用 quarkus-kubernetes 扩展并且具有绑定到 CustomResource 定义的 Kotlin 类(就像您构建运算符一样)时,您需要知道底层的 Fabric8 Kubernetes 客户端使用其自己的静态 Jackson ObjectMapper,可以使用 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)

请仔细测试此编译到本机映像,如果遇到问题,则回退到与 Java 兼容的 Jackson 绑定。

协程支持

扩展

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

扩展 注释

quarkus-rest

为 Jakarta REST 资源方法提供支持

quarkus-rest-client

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

quarkus-messaging

为 Reactive 消息传递方法提供支持

quarkus-scheduler

为调度器方法提供支持

quarkus-smallrye-fault-tolerance

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

quarkus-vertx

@ConsumeEvent 方法提供支持

quarkus-websockets-next

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

Kotlin 协程和 Mutiny

Kotlin 协程提供了一种命令式编程模型,实际上是以异步、响应式的方式执行的。为了简化 Mutiny 和 Kotlin 之间的互操作性,有一个模块 io.smallrye.reactive:mutiny-kotlin此处 描述了该模块。

CDI @Inject 与 Kotlin

Kotlin 反射注释处理与 Java 不同。使用 CDI @Inject 时,您可能会遇到错误,例如:“kotlin.UninitializedPropertyAccessException: lateinit property xxx has not been initialized”

在下面的示例中,可以通过调整注释,添加 @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)
    }

}

相关内容