torutkのブログ

ソフトウェア・エンジニアのブログ

依存するJARがあるJavaFXアプリケーションの実行可能JAR作成

JavaFX Advent Calendar 12月3日担当です。

11/30開催の「第8回JavaFX勉強会」(日本JavaFXユーザーグループ主催)で、「ダブルクリックで起動する JavaFX アプリケーション JAR の仕組み 」を発表をしてきました。

この発表では、作成するプログラムがJava SE/JavaFX以外の他のJARに依存しない単純な形式を対象にしていました。
発表の質疑応答にて、依存するJARがあり複数のJARで構成されるアプリケーションの場合について質問を受けました。事前に調査はできておらず、宿題ということで本日「(補遺)依存するJARがあるJavaFXアプリケーションの実行可能JAR作成」を紹介します。

JavaFXの実行可能JARのおさらい

Java SE 7 Update6からJavaFXのランタイム(実行に必要なファイル群)を同じディレクトリに含むようになりました。しかし、JavaFXJava SE 7の標準ライブラリではないため、実行にあたってはJavaFXのランタイムライブラリにクラスパスを通すことが必要です。

では、JavaFXアプリケーション実行可能JARを実行するときに、-classpathオプションでJavaFXランタイムを指定すれば?と思いますが、それはjavaコマンドの仕様でできません。
JDK 6のjavaコマンドのドキュメントにある-jarオプションの説明から抜粋します。

このオプションを使用すると、指定した JAR ファイルがすべてのユーザークラスのソースになり、ユーザークラスパスのほかの設定は無視されます。

つまり、実行可能JARに必要なアプリケーションをすべて含めるか、実行可能JARのマニフェストファイル(MANIFEST.MF)に記述する、相対パスでで指定できる場所に置いたJARファイルしか利用できません。

そこで、JavaFXアプリケーションの場合は、アプリケーションのmainを呼ぶ前に、ブートストラップとしてJavaFXランタイムを探してクラスパスに追加するためのクラス群を実行可能JARに組み込み、ここからアプリケーションを実行するような特別な実行可能JARを作る仕掛けを提供しています。この仕掛けを利用して実行可能JARを作成するコマンドがjavafxpackagerです。また、Antから利用しやすいようAntタスクも提供されています。

依存するJARを持つJavaFXアプリケーション

検証のために、Java SEランタイム/JavaFXラインタイム以外のライブラリ(JARファイル)に依存を持つアプリケーションを作成します。今回は、Twitter4jを使ってみました*1

まず、NetBeans 7.3 Beta2で(7.2でもいいです)、[ファイル]メニュー>[新規プロジェクト]>[JavaFX]>[JavaFX FXMLアプリケーション]を選択します*2

JavaFX FXMLアプリケーションで生成される雛形は、Applicationのサブクラス、コントローラクラス、そしてFXMLファイルと3つから構成されます。画面はボタンとラベルからなる単純なものです。

プロジェクトのプロパティで、ライブラリにTwitter4jを追加します。単純なサンプルならtwitter4j-core-3.0.1.jarだけで十分なようです。

これで、アプリケーションのJAR以外にtwitter4j-core-3.0.1.jarを必要とするJavaFXアプリケーションが出来上がります。

NetBeansでビルドすると、ビルド成果物が置かれるディレクトリは次のようになります。

TwitterFX
  +-- build
  +-- dist
  |     +-- lib
  |     |     +-- twitter4j-core-3.0.1.jar
  |     +-- web-files
  |     |     +-- dtjava.js
  |     |     :
  |     |     +-- upgrade_javafx.png
  |     +-- TwitterFx.html
  |     +-- TwitterFx.jar
  |     +-- TwitterFx.jnlp

生成されたJAR(TwitterFx.jar)のマニフェストファイルを調べてみると、

Manifest-Version: 1.0
implementation-vendor: torutk
JavaFX-Version: 2.2
implementation-title: TwitterFx
implementation-version: 1.0
JavaFX-Application-Class: twitterfx.TwitterFx
JavaFX-Class-Path: TwitterFx.jar lib/twitter4j-core-3.0.1.jar
Created-By: JavaFX Packager
Main-Class: com/javafx/main/Main
  :(後略)

となっています。

JavaFX-Class-Pathという項目に、依存するJAR(twitter4j-core-3.0.1.jar)の相対パスが定義されています。

配置されたJARのダブルクリック起動

TwitterFx/dist/TwitterFx.jar をエクスプローラでダブルクリックすると起動します。

TwitterFx.jarだけ別ディレクトリにコピーし実行すると、エラーダイアログが表示されます。

コマンドプロンプトから、このコピーしたJARを実行してみると、エラー詳細が分かります。

Caused by: java.lang.ClassNotFoundException: twitter4j.TwitterException
        at java.net.URLClassLoader$1.run(Unknown Source)

と、twitter4jのクラスが見つからないというものです。

そこで、先のdistディレクトリの下、libディレクトリとその中に含まれているtwitter4j-core-3.0.1.jarを一緒にコピーします。

work
  +-- TwitterFx.jar
  +-- lib
        +-- twitter4j-core-3.0.1.jar

となるように置き、実行します。
すると、エクスプローラ上からダブルクリックして実行できるようになります。もちろん、コマンドプロンプトからでも実行可能です。

javafxpackagerコマンドで依存JARを持つアプリケーションの実行可能JARを生成する

OracleJavaFXドキュメント(deploy)でjavapackagerのコマンドラインオプションを見てみると、

-classpath <files>
  List of dependent JAR file names.

というのがあります。

これを使って、次のように実行可能JavaFXアプリケーションJARを作成します。

c:\work\TwitterFx> javafxpackager -createjar 
 -appclass twitterfx.TwitterFx
 -srcdir build\classes -outdir dist -outfile TwitterFx-hand.jar
 -classpath lib\twitter4j-core-3.0.1.jar

生成されたTwitterFx-hand.jarと、そのJARファイルがある場所を基点に相対パスでlib\twitter4j-core-3.0.1.jarを用意して実行すると、無事起動に成功しました。

javafxpackagerで生成したJARに含まれるマニフェストファイルを見ると、

JavaFX-Class-Path: lib/twitter4j-core-3.0.1.jar

と、-classpathで指定したJARだけ記載されています。NetBeansで生成したときとちょっと記述が違っています。NetBeansで生成した場合、依存するJARの他、アプリケーション自身のJARも記載されています。javafxpackagerの動作の方が正解かと思いますが。

java.io.IOException: ファイル名、ディレクトリ名、またはボリューム ラベルの構文が
間違っています。
        at java.io.WinNTFileSystem.canonicalize0(Native Method)
        at java.io.Win32FileSystem.canonicalize(Win32FileSystem.java:414)
        at java.io.File.getCanonicalPath(File.java:589)
        at java.io.File.getCanonicalFile(File.java:614)
        at com.javafx.main.Main.fileToURL(Main.java:97)
複数の依存するJARを-classpathで指定する
c:\work\TwitterFx> javafxpackager -createjar 
 -appclass twitterfx.TwitterFx
 -srcdir build\classes -outdir dist -outfile TwitterFx-hand.jar
 -classpath lib\twitter4j-async-3.0.1.jar;lib\twitter4j-core-3.0.1.jar

と、javaやjavacの-classpathオプション指定と同じ書式で複数のJARを指定することができます。

まとめ

JavaFXアプリケーションの起動方式4種のうちの1つ、スタンドアロン・プログラム用に作成される実行可能JARファイルは、依存するJARファイルを指定するのにJARの中に含まれるMANIFEST.MFを使用します。

JARのマニフェストでは、相対パスのみ有効です。

実行時には、JARのマニフェストで指定される依存するJARの相対パスに合致するファイルが必要です。

javafxpackagerコマンドでコマンドラインオプション-classpathで依存するJARを指定します。

*1:思い立ってダウンロードして初めて使ってみました。別にlog4jとかの慣れたJARでよかったのですが、Java EEの紹介で日本オラクルの寺田さんがTwitter4jを使ったデモをしているのを見て、なんとなく使ってみようという程度のものです。

*2:Scene BuilderでFXMLの編集をビジュアルにしてみたかっただけで、JavaFXアプリケーションでも今回の調査は達成できます。