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 项目。这可以通过使用以下命令来完成:
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 指南。
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
:
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
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
:
<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 插件,它隐式添加
sourceDirectory
和testSourceDirectory
构建属性来指向 Kotlin 源(分别为src/main/kotlin
和src/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 源代码并立即看到其更改反映。
要了解此功能的操作,请首先执行:
quarkus dev
./mvnw quarkus:dev
./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 插件中配置它们:
<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>
quarkusDev {
compilerOptions {
compiler("kotlin").args(['-Werror'])
}
}
tasks.quarkusDev {
compilerOptions {
compiler("kotlin").args(["-Werror"])
}
}
Packaging the application
和往常一样,可以使用以下命令打包应用程序:
quarkus build
./mvnw install
./gradlew build
并使用 java -jar target/quarkus-app/quarkus-run.jar
执行。
您还可以使用以下命令构建本机可执行文件:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
Kotlin and Jackson
如果项目中添加了 com.fasterxml.jackson.module:jackson-module-kotlin
依赖项和 quarkus-jackson
扩展(或 quarkus-resteasy-jackson
或 quarkus-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 |
---|---|
|
为 Jakarta REST 资源方法提供支持 |
|
为 REST 客户端接口方法提供支持 |
|
为消息传递方法提供支持 |
|
为调度器方法提供支持 |
|
为基于声明的注释 API 提供支持 |
|
为 |
|
为服务器端和客户端端点方法提供支持 |
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)
}
}