The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

使用Kotlin

Kotlin 是一种非常流行的基于JVM的编程语言(在其他环境中)。在过去的几年里,Kotlin的受欢迎程度激增,使其成为最受欢迎的JVM语言,当然,除了Java之外。

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

这项技术被认为是preview。

preview(预览) 中,不保证向后兼容和在生态系统中的存在。具体的改进可能需要改变配置或API,并且正在计划变得 稳定 。欢迎在我们的 邮件列表 中提供反馈,或在我们的 GitHub问题列表 中提出问题。

For a full list of possible statuses, check our FAQ entry.

先决条件

要完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • 安装JDK 11以上版本并正确配置了 JAVA_HOME

  • Apache Maven 3.8.1+

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

如果用Mandrel构建,确保使用Mandrel 22.1或以上的版本,例如 ubi-quarkus-mandrel:22.1-java17 。对于旧版本,当您试图反序列化有空字段或缺失字段的JSON文档时,可能会遇到错误,类似于 link:#kotlin-jacksonKotlin和Jackson 部分提到的错误。

注意:关于Gradle项目的设置,请见下文,如需进一步参考,请查阅 Gradle设置页面 的指南。

创建Maven项目

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

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

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

关于如何安装并使用Quarkus CLI的更多信息,请参考Quarkus CLI指南

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-kotlin-quickstart \
    -Dextensions="kotlin,resteasy-reactive-jackson"
cd rest-kotlin-quickstart

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

When adding kotlin to the extensions list, the Maven plugin will generate a project that is properly configured to work with Kotlin. Furthermore, the org.acme.ReactiveGreetingResource class is implemented as Kotlin source code (as is the case with the generated tests). The addition of resteasy-reactive-jackson in the extension list results in importing the RESTEasy Reactive and Jackson extensions.

ReactiveGreetingResource looks like this:

ReactiveGreetingResource.kt
package org.acme

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

@Path("/hello")
class ReactiveGreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    fun hello() = "Hello from RESTEasy Reactive"
}

更新代码

In order to show a more practical example of Kotlin usage we will add a simple data class called Greeting like so:

Greeting.kt
package org.acme.rest

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

We also update the ReactiveGreetingResource class like so:

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

@Path("/hello")
class GreetingResource {

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

经过这些更改, /hello 端点将用一个JSON对象而不是一个简单的字符串来响应请求。

To make the test pass, we also need to update ReactiveGreetingResourceTest like so:

import org.hamcrest.Matchers.equalTo

@QuarkusTest
class ReactiveGreetingResourceTest {

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

}

Kotlin version

The Quarkus Kotlin extension already declares a dependency on some base Kotlin libraries like kotlin-stdlib-jdk8 and kotlin-reflect. The Kotlin version of these dependencies is declared in the Quarkus BOM and is currently at {kotlin-version}. It is therefore recommended to use the same Kotlin version for other Kotlin libraries. When adding a dependency to another base Kotlin library (e.g. kotlin-test-junit5) you don’t need to specify the version, since the Quarkus BOM includes the Kotlin BOM.

This being said, you still need to specify the version of the Kotlin compiler to use. Again, it is recommended to use the same version which Quarkus uses for the Kotlin libraries.

Using a different Kotlin version in a Quarkus application is typically not recommended. But in order to do so, you must import the Kotlin BOM before the Quarkus 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=javax.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 Enables the all-open annotation plugin (see discussion below)

需要注意的是,这里使用了 all-open 的Kotlin编译器插件。为了理解为什么需要这个插件,首先我们需要注意:在默认情况下,所有从Kotlin编译器生成的类都被标记为 final

然而,拥有 final 类并不能很好地与需要创建 动态代理 的各种框架配合使用。

因此, all-open Kotlin编译器插件允许我们对编译器进行配置,使其 将有某些注解的类标记为 final 。在上面的片段中,我们已经指定了带有 javax.ws.rs.Path 注解的类不应该是 final 的 。

If your application contains Kotlin classes annotated with javax.enterprise.context.ApplicationScoped for example, then <option>all-open:annotation=javax.enterprise.context.ApplicationScoped</option> needs to be added as well. Same goes for any class that needs to have a dynamic proxy created at runtime.

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 "{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-resteasy-reactive'
    implementation 'io.quarkus:quarkus-resteasy-reactive-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("javax.ws.rs.Path")
    annotation("javax.enterprise.context.ApplicationScoped")
    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指南,需要配置all-open插件

或者,如果您使用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-resteasy-reactive")
    implementation("io.quarkus:quarkus-resteasy-reactive-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("javax.ws.rs.Path")
    annotation("javax.enterprise.context.ApplicationScoped")
    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指南,需要配置all-open插件

Overriding the Quarkus BOM Kotlin version (Gradle)

If you want to use a different version than the one specified by Quarkus' BOM in your application (for example, to try pre-release features or for compatibility reasons), you can do so by using the strictly {} version modifier in your Gradle dependencies. For instance:

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

当执行一个对 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源代码进行修改时,实时重载功能是不可用的。我们希望在未来能缓解这一限制。

配置实时重载编译器

如果您需要定制 kotlinc 在开发模式下使用的编译标志(compiler flags),您可以在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 clean package
Gradle
./gradlew build

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

您也可以用以下方法构建原生(native)可执行文件:

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

Kotlin和Jackson

如果 com.fasterxml.jackson.module:jackson-module-kotlin 依赖和 quarkus-jackson 扩展(或 quarkus-resteasy-jacksonquarkus-resteasy-reactive-jackson 其中之一)已经被添加到项目中,那么 Quarkus 会自动将 KotlinModule 注册到 ObjectMapper Bean 中(更多细节请参见 这篇指南 )。

当使用Kotlin数据类与 native-image 时,尽管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)

To ensure full-compatibility with native-image, it is recommended to apply the Jackson @field:JsonProperty("fieldName") annotation, and set a nullable default, as illustrated below. You can automate the generation of Kotlin data classes for your sample JSON using IntelliJ IDEA plugins (such as JSON to Kotlin Class), and easily enable the Jackson annotation and select nullable parameters as part of the auto-code generation.

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和Kubernetes客户端

当使用 quarkus-kubernetes 扩展并让Kotlin类绑定到CustomResource定义时(就像您在构建operator时一样),您需要注意底层的Fabric8 Kubernetes客户端使用它自己的静态Jackson ObjectMapper 对象,您可以这样为其配置 KotlinModule

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

...

Serialization.jsonMapper().registerModule(KotlinModule())
Serialization.yamlMapper().registerModule(KotlinModule())

请在编译到native-image时仔细测试,如果遇到问题,请退回到与Java兼容的Jackson binding。

Coroutines 支持

扩展

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

扩展 备注

quarkus-resteasy-reactive

为JAX-RS Resource方法提供支持

quarkus-rest-client-reactive

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

quarkus-smallrye-reactive-messaging

提供对响应式信息传递方法的支持

quarkus-scheduler

为调度器方法提供支持

quarkus-smallrye-fault-tolerance

对基于声明性注解的API提供支持

Kotlin coroutines和Mutiny

Kotlin coroutines provide an imperative programming model that actually gets executed in an asynchronous, reactive manner. To simplify the interoperability between Mutiny and Kotlin there is the module io.smallrye.reactive:mutiny-kotlin, described here.

使用Kotlin的CDI @Inject

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

在下面的示例中,通过调整注解,添加@field:Default来处理Kotlin反射注解定义中缺少@Target,就可以很容易地解决这一问题。

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

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.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 javax.enterprise.context.ApplicationScoped

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.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)
    }

}