Gradle Wrapperを経由せずに実行する

Gradleでは基本的にGradleラッパーを介してプログラムが実行される。 しかしこのラッパーはメモリを20MBほど消費し、CPUにも多少負荷をかけている。 その為、直接プログラムを実行したい。

build.gradleに下記を書く。

apply plugin:'application'
mainClassName = "org.gradle.sample.Main"

org.gradle.sample.Mainには、mainメソッドが存在するクラスを指定する。

そして次のコマンドを実行する。

% gradle clean installDist

するとbuild/install/<プログラム名>/bin/<プログラム名> というファイルが作られるのでそれを実行する。

ここで次のような例外が出た。

Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.NullPointerException

今までrunなどのGradleのタスクから実行していた時には出なかった例外だ。

例外が発生しているのは以下のコード。

URL dirUrl = WordReader.class.getClassLoader().getResource("dir");
File[] langFiles = new File(dirUrl.getPath()).listFiles();

for (File f : langFiles) {
  System.out.println(f.getName());
}

例外はfor文の書き出しのところで起きている。 つまりlangPathsがnullとなっている。

この時、dirUrlは下記のようになっていた。

<プロジェクトルート>/build/install/<プログラム名>/lib/<プログラム名>.jar!/dir

つまり、jarファイル内のdirフォルダを読み込むのに失敗している。

gradle runした場合は下記のようになっていた。

<プロジェクトルート>/build/resources/main/dir

jarファイル内のファイルを読み込むのは簡単だが、フォルダを読み込むのは難しい。

http://stackoverflow.com/questions/11012819/how-can-i-get-a-resource-folder-from-inside-my-jar-file

これをなるべくコードは汚さず、runでもjarからでも動くようにしたい。

jarの外に、jar内にあるリソースファイルと同じもののシンボリックリンクを作り、それを読み込むことにした。 冗長だし、libフォルダの中に作ることになるので気持ち悪いが、一番コードを汚さず手軽な方法だと思う。

まず、

URL dirUrl = WordReader.class.getClassLoader().getResource("dir");

としていたところを、

URL dirUrl = WordReader.class.getClassLoader().getResource("./dir");

とする。これで、jarからだと、

<プロジェクトルート>/build/install/<プログラム名>/lib/dir

を読み込もうとし、runからだと前述と変わらないパスを読み込もうとする。

そして以下のコマンドを実行する。

% ./gradlew clean installDist
% ln -s `pwd`/build/resources/main/* build/install/プログラム名/lib/

installDistを実行した時には build/resourcesという、ソースにあるリソースのコピーが作成される。 その中にある全てのシンボリックリンクをlibフォルダ内に作っている。

あとは前述の、生成された起動スクリプトを実行すれば動くはず。

まとめ

メモリをケチってやることでもない。