オートリロードで時間節約

Estimated reading time: 2 minutes

開発中は、フィードバックループのサイクルを高速にすることが重要です。 多くの場合、サーバーの再起動には時間がかかることがあるため、Ktorには、アプリケーションクラスをリロードする基本的なオートリロード機能が用意されています。

オートリロードはJava 9以上ではexperimentalな機能です。 もし動作しなかった場合はお気軽にこちらからIssueを立て、その場合はJDK 8を使用してください。

オートリロードを使用すると、パフォーマンスが低下します。 そのため、実稼働環境やベンチマークを実行する際には使用しないでください。

目次:

class変更時のオートリロード

組み込みサーバーまたは設定ファイルのどちらを利用する場合でも、 監視したいクラスローダーにマッチする監視対象の部分文字列リストを提供する必要があります。

例えばgradleを使用する場合の典型的なクラスローダーは次のようになります:

/Users/user/projects/ktor-exercises/solutions/exercise4/build/classes/kotlin/main

この場合、solutions/exercise4exercise4を使うことでクラスローダーにマッチします。

組み込みサーバーを使う場合

カスタムのmainメソッドとembeddedServerを使用する場合、オプションのパラメーターwatchPathsを使用して、監視及びオートリロードされるサブパスのリストを指定できます。

fun main(args: Array<String>) {
    embeddedServer(
        Netty,
        watchPaths = listOf("solutions/exercise4"),
        port = 8080,
        module = Application::mymodule
    ).apply { start(wait = true) }
}

fun Application.mymodule() {
    routing {
        get("/plain") {
            call.respondText("Hello World!")
        }
    }
}

watchPathsを使用する場合、サーバーを構成するためのモジュールににラムダを使用するのではなく、メソッド参照を提供する必要があります。

メソッド参照の代わりにラムダを使用しようとすると、次のエラーが表示されます:

Exception in thread "main" java.lang.RuntimeException: Module function provided as lambda cannot be unlinked for reload

このエラーを修正するには、次のようにラムダ本体をApplicationの拡張メソッド(モジュール)に抽出するだけです:

❌ Code that won’t work:

fun main(args: Array<String>) {
    // ERROR! Module function provided as lambda cannot be unlinked for reload
    embeddedServer(Netty, watchPaths = listOf("solutions/exercise4"), port = 8080) {
        routing {
            get("/") {
                call.respondText("Hello World!")
            }
        }
    }.start(true)
}

✅ Code that will work:

fun main(args: Array<String>) {
    embeddedServer(
        Netty, watchPaths = listOf("solutions/exercise4"), port = 8080,
        // GOOD!, it will work 
        module = Application::mymodule
    ).start(true)
}

// Body extracted to a function acting as a module
fun Application.mymodule() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
    }
}

application.confを使う場合

設定ファイルを使用する場合: 例えばEngineMainを使用し、コマンドラインから実行する場合やホストされているサーバーコンテナ内から実行する場合を考えてみます。

この機能を有効にするにはwatchキーをktor.deployment設定に追加します。

watch - 監視され、自動的にリロードされるクラスパスエントリの配列

ktor {
    deployment {
        port = 8080
        watch = [ module1, module2 ]
    }
    
    
}

現時点では、watchキーはロードされたクラスパスエントリやjar名、プロジェクトディレクトリ名などに対してcontainsで一致する単なる文字列です。 これらのクラスは、変更が検出されたときにリサイクルされる特別なClassLoaderでロードされます。

ktor-server-coreクラスは特にオートリロードから除外されているため、ktor自体でなにか作業をしている場合は自動的にリロードされると思わないでください。 オートリロードが開始される前にコアクラスがロードされるため、機能しません。 除外は潜在的に小さくすることができますが、起動中にロードされた型のすべての推移的閉包を分析することは困難です。

クラスパスのエントリはfile:///path/to/project/build/classes/myproject.jarのように見えるため、to/projectは一致しますが、com.mydomainは一致しません。

ソースの変更時に自動的に再コンパイルする

オートリロード機能はクラスファイルの変更のみを検出するため、アプリケーションを自分でコンパイルする必要があります。

IntelliJ IDEAを実行中に、Build -> Build Projectで実行できます。

ただし、gradleを使用してソースの変更を自動的に検出し、自動的にコンパイルすることもできます。プロジェクトフォルダー内の別のターミナルでgradle -t installDistを実行するだけです。

これはアプリケーションをコンパイルし、その後追加のソース変更を監視し、必要に応じて再コンパイルします。したがってオートリロードをトリガーします。

その後、別のターミナルを使用してgradle runでアプリケーションを実行できます。

もしIntelliJ IDEAを使ってアプリケーションを起動している場合は、compilation output locationsを適切に設定する必要があります。 なぜならIntelliJはgradleが利用している出力場所とは異なる場所を利用するからです。

次の例を考えてみましょう:

build.gradleを利用するか、IDE内で直接アプリケーションを実行できます。

サンプルファイルでmainメソッドを実行するか、io.ktor.server.netty.EngineMain.mainを実行します。 commandLineEnvironment を使用するEngineMainは application.conf ファイルの読み込みをサポートします(HOCON形式)。

main.kt
package io.ktor.exercise.autoreload

import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*

// Exposed as: `static void io.ktor.exercise.autoreload.MainKt.main(String[] args)`
fun main(args: Array<String>) {
    //io.ktor.server.netty.main(args) // Manually using Netty's EngineMain
    embeddedServer(
        Netty, watchPaths = listOf("solutions/exercise4"), port = 8080,
        module = Application::module
    ).apply { start(wait = true) 
}

// Exposed as: `static void io.ktor.exercise.autoreload.MainKt.module(Application receiver)`
fun Application.module() {
    routing {
        get("/plain") {
            call.respondText("Hello World!")
        }
    }
}
application.conf
ktor {
    deployment {
        port = 8080
        watch = [ solutions/exercise4 ]
    }

    application {
        modules = [ io.ktor.exercise.autoreload.MainKt.module ]
    }
}

ご覧の通り、監視したいクラスローダー(この場合はsolutions/exercise4のみ)に一致する文字列のリストを指定する必要があります。これは変更時に再読込する必要があります。