Posted at

Jarの中のリソースファイルをどこかにコピーしたい

More than 1 year has passed since last update.

Javaアプリ開発では、クラスファイルやリソースファイルを一つのJarにまとめてパッケージングするのが一般的です。

MavenやGradleプロジェクトであれば、 src/main/resources ディレクトリに置くのが一般的でしょうか。

時々、リソースに含めたファイルをJar外にコピーして使いたいことがあります。

(設定ファイルのテンプレートを外に出したり、配布の都合で同梱していた実行ファイルを実行したいときなど)

resources ディレクトリに配置したファイル hoge.txt のURIは以下のコードで取得できます。

ClassLoader.getSystemResource("hoge.txt").toURI()

URIがわかればあとはこれを所望の場所にコピーすればいいのですが、Jarにした場合とそうでない場合で違いがあってはまりました。


Jarにパッケージングしないでリソースを参照した時のURI

動作試験のために ./gradlew :run とかした場合ですね。

Jarにパッケージングせずに実行した場合は、リソースのURIは file: プロトコルで、リソースファイルの場所を指します。

file:/path/to/project/build/resources/main/hoge.txt

このURIだと File クラスのコンストラクタに渡すことができるので、Kotlinの拡張メソッド File.copyTo() でコピーが可能です。

File(ClassLoader.getSystemResource("hoge.txt").toURI()).copyTo(destination)

しかしJarにパッケージングしてしまうと、URIをFileクラスコンストラクタに渡した途端に例外が出ます。


Jarにパッケージングされたリソースを参照した時のURI

jar: スキームのURIが得られます。

jar:file:/path/to/jar/file/MyApp.jar!/hoge.txt

これをそのままFileのコンストラクタに渡すと java.lang.IllegalArgumentException: URI is not hierarchical が発生します。

コピー元Fileが得られればコピーが楽なのですが、得られないのでそうはいきません。

どうすればいいかと言うと、リソースを開くInputStreamから内容を取り出して、コピー先ファイルに書き込んでしまえば良さそうでした。

自力でコピーメソッドを書くこともできますが、便利なメソッドがすでにあるので使わせてもらいましょう。

JDK付属の Files.copy() (Java 1.7から実装)を使うか、Commons IOFileUtils.copyURLToFile()FileUtils.copyToFile() を使うのがお手軽でしょう。

val srcIs: InputStream = ClassLoader.getSystemResourceAsStream("hoge.txt")

val srcUrl: URL = ClassLoader.getSystemResource("hoge.txt")

// Files.copy() の場合
Files.copy(srcIs, destination.toPath())

// FileUtils.copyURLToFile()の場合
FileUtils.copyURLToFile(srcUrl, destination)

// FileUtils.copyToFile()の場合
FileUtils.copyToFile(srcIs, destination)

とりあえずリソースファイルはURLやURIで扱うのではなく、Streamで扱ったら間違いないようです。