Jar出力して実行したら、resources内のファイルが見つからない

resources内のファイルを読み込み

以下のコードは、KtorでFirebaseの初期化をするためにfirebase-adminsdk.jsonFileInputStreamで読み込んでいます。

// Application.kt
fun Application.initializeFirebaseApp() {
    val serviceAccount = FileInputStream("resources/firebase-adminsdk.json")
    val databaseUrl = "DATABASE_URL"
    val storageUrl = "STORAGE_URL"

    val options = FirebaseOptions.builder()
        .setCredentials(GoogleCredentials.fromStream(serviceAccount))
        .setDatabaseUrl(databaseUrl)
        .setStorageBucket(storageUrl)
        .build()

    if (FirebaseApp.getApps().isEmpty()) FirebaseApp.initializeApp(options)
}

このコードを実行するApplication.ktfirebase-adminsdk.jsonは以下のように配置しています。 f:id:go_takahana:20210307112845p:plain

IDEでビルドしてデバッグしている分には問題なく動作しますが、Jar出力して実行するとjava.io.FileNotFoundExceptionがスローされてしまいます。

Caused by: java.io.FileNotFoundException: resources/firebase-adminsdk.json (No such file or directory)

解決方法

FileInputStreamを取得するコードを修正します。

// Application.kt
fun Application.initializeFirebaseApp() {
       val serviceAccount = javaClass.classLoader.getResourceAsStream("firebase-adminsdk.json")
        // ...
}

また、build.gradle.ktssourceSetsの設定が必要です。

kotlin.sourceSets["main"].kotlin.srcDirs("src/main")
sourceSets["main"].resources.srcDirs("resources")

getResourceAsStreamとは?

実装に必要なリソースファイル(テキスト、イメージ)を読み込むことができます。 ドキュメントでは「位置に依存しない方法でのリソースへのアクセス」の方法として説明させています。

今回のようにフォルダ構造に合わせてパスを指定して問題があるときは、getResourceAsStreamを使うのが良さそうです。

そもそも絶対パス相対パスで指定していると、リファクタしにくいので適切にsourceSets及びresourcesを使用した方が良さそうです。