Kotlinの使用
Kotlin は、JVMをターゲットにした(他の環境を含めても)非常に人気のあるプログラミング言語です。Kotlinはここ数年で人気が急上昇し、Javaを除いて最も人気のあるJVM言語となっています。
このガイドで説明するように、QuarkusはKotlinを使用するためのファーストクラスのサポートを提供しています。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.9
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
注:Gradleプロジェクトのセットアップについては、以下を参照してください。また、詳細については、 Gradleセットアップページのガイドを参照してください。
Mavenプロジェクトの作成
まず、新しいKotlinプロジェクトが必要です。これは以下のコマンドで行うことができます。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=rest-kotlin-quickstart"
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"
}
コードの更新
Kotlin のより実用的な使用例を示すために、 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オブジェクトで応答するようになりました。
テストをパスするためには、 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-stdlib-jdk8
や kotlin-reflect
などの一部のKotlinベースライブラリへの依存を宣言しています。これらの依存関係のKotlinバージョンは、Quarkus BOMで宣言されており、現在のバージョンは2.0.21です。したがって、他のKotlinライブラリには、同じKotlinバージョンを使用することをお勧めします。他のベースKotlinライブラリに依存性を追加する場合(たとえば、 kotlin-test-junit5
)、Quarkus BOMに Kotlin BOM が含まれているため、バージョンを指定する必要はありません。
そうは言っても、使用する Kotlin コンパイラのバージョンを指定する必要があります。 繰り返しになりますが、Quarkus が Kotlin ライブラリに使用するのと同じバージョンを使用することをお勧めします。
通常、Quarkus アプリケーションで別の Kotlin バージョンを使用することはお勧めしません。 ただしそうする必要がある場合は、Quarkus BOM の 前に Kotlin BOM をインポートする必要があります。 |
重要なMavenの設定ポイント
生成された pom.xml
には、Kotlin が選択されていない場合と比較して、以下の修正が含まれています。
-
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
クラスであることは、 ダイナミックプロキシー を作成する必要がある様々なフレームワークではうまく機能しません。
そのため、 all-open
Kotlinコンパイラ・プラグインでは、特定のアノテーションを持つクラスを final
としてマーク *しない * ようにコンパイラを設定することができます。上記のスニペットでは、 jakarta.ws.rs.Path
のアノテーションを持つクラスは final
としないように指定しています。
例えば、 jakarta.enterprise.context.ApplicationScoped
でアノテーションされた Kotlin クラスがアプリケーションに含まれている場合、 <option>all-open:annotation=jakarta.enterprise.context.ApplicationScoped</option>
も追加する必要があります。JPAエンティティクラスのように、実行時に動的プロキシを作成する必要があるクラスも同様です。
Quarkusの将来のバージョンでは、この設定を変更する必要がないようにKotlinコンパイラプラグインを設定するようになる予定です。
重要なGradle設定ポイント
Mavenの設定と同様、Gradleを使用する場合、Kotlinを選択すると以下のような修正が必要になります:
-
quarkus-kotlin
アーティファクトが依存関係に追加されています。このアーティファクトはライブリロードモードでの Kotlin のサポートを提供します (これについては後ほど説明します)。 -
kotlin-stdlib-jdk8
も依存関係として追加されています。 -
Kotlin プラグインが有効になり、暗黙のうちに
sourceDirectory
とtestSourceDirectory
のビルドプロパティーが追加され、Kotlin ソース (それぞれsrc/main/kotlin
とsrc/test/kotlin
) を指すようになります。 -
all-open Kotlinプラグインは、アノテーションがハイライトされているクラスを最終的なものとしてマークしないようにコンパイラに指示します (必要に応じてカスタマイズしてください)
-
ネイティブイメージを使用する場合は、http (または https) プロトコルの使用を宣言しなければなりません。
-
以下に設定例を示します:
plugins {
id 'java'
id 'io.quarkus'
id "org.jetbrains.kotlin.jvm" version "2.0.21" (1)
id "org.jetbrains.kotlin.plugin.allopen" version "2.0.21" (1)
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.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_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ガイドにあるように、all-open設定が必要です。 |
または、GradleのKotlin DSLを使用している場合:
plugins {
kotlin("jvm") version "2.0.21" (1)
kotlin("plugin.allopen") version "2.0.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_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ガイドにあるように、all-open設定が必要です。 |
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"
}
}
ライブ・リロード
Quarkusは、ソースコードに加えられた変更をライブでリロードする機能をサポートしています。このサポートはKotlinでも利用できます。つまり、開発者はKotlinのソースコードを更新して、変更が反映されたことをすぐに確認することができます。
この機能の動作を確認するには、まず次のコマンドを実行します:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
http://localhost:8080/hello
に対してHTTP GETリクエストを実行すると、 message
フィールドに hello
という値を持つJSONメッセージが表示されます。
ここで、お好きなエディタやIDEを使って、 ReactiveGreetingResource.kt
をアップデートし、 hello
のメソッドを以下のように変更します:
fun hello() = Greeting("hi")
http://localhost:8080/hello
に対してHTTP GETリクエストを実行すると、 message
フィールドに hi
という値を持つJSONメッセージが表示されるはずです。
注意点としては、お互いに依存関係にあるJavaとKotlinの両方のソースに変更を加えた場合、ライブリロード機能が利用できないということです。将来的にはこの制限を緩和したいと考えています。
ライブリロードコンパイラーの設定
開発モードで 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"])
}
}
アプリケーションのパッケージング
いつものように、アプリケーションは以下の方法でパッケージ化されます:
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 と Jackson
com.fasterxml.jackson.module:jackson-module-kotlin
依存関係と quarkus-jackson
エクステンション(または quarkus-resteasy-jackson
または quarkus-rest-jackson
エクステンションのいずれか)がプロジェクトに追加されている場合、Quarkus は自動的に KotlinModule
を ObjectMapper
Bean に登録します(詳細は この ガイドを参照)。
Kotlin のデータクラスを native-image
で使用すると、Kotlin Jackson Module が登録されているにもかかわらず、 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")
アノテーションを適用し、以下の図のように nullable のデフォルトを設定することをお勧めします。Intellijのプラグイン(JSON to Kotlin Classなど)を使用して、サンプルのJSONに対するKotlinデータクラスの生成を自動化し、Jacksonアノテーションを有効にして、自動コード生成の一部としてnullableパラメータを選択することができます。
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 定義にバインドする場合(オペレーターを構築する場合など)、基盤となる Fabric8 Kubernetes Client が独自の静的な 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のコルーチンをサポートするために、メソッドシグネチャでKotlinの suspend
キーワードを使用できるようにするものです。
エクステンション | Comments |
---|---|
|
Jakarta REST Resource Methodsへのサポートが提供されます |
|
RESTクライアントインタフェースメソッドへのサポートが提供されます |
|
Reactiveメッセージングメソッドへのサポートが提供されます |
|
スケジューラーメソッドへのサポートが提供されます |
|
宣言型アノテーションベースAPIへのサポートが提供されます |
|
Support is provided for |
|
Support is provided for server-side and client-side endpoint methods |
Kotlin coroutines と Mutiny
Kotlinのコルーチンは、非同期でリアクティブな方法で実際に実行される命令型プログラミングモデルを提供します。MutinyとKotlinの相互運用を簡単にするために、モジュール io.smallrye.reactive:mutiny-kotlin
が存在し、 ここ で解説されています。
Kotlinでの CDI @Inject
Kotlinのリフレクションアノテーション処理はJavaとは異なります。CDIの@Injectを使用していると、以下のようなエラーが発生することがあります。"kotlin.UninitializedPropertyAccessException: lateinitプロパティーxxxが初期化されていません"
以下の例では、アノテーションに @field を追加することで、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では、アノテーション定義に@Targetがないため、@field: xxx修飾子が必要です。この例では @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)
}
}