指摘、要望などはこちらからお願いします。

JARファイルをダブルクリックで実行


更新日:2010/07/11


目標

Javaアプリケーションを開発し、JARファイルとして配布する場合、配布先PCで
JARファイルをダブルクリックするだけで実行できるようにする。
この時、配布先PCでは外部ライブラリのインストールやシステムの設定等を全く
行わなくても良いようにする。
また、配布物はJARファイル1つだけにする。

目標とするJARファイルの内部構造イメージ。
test.jar
|  META-INF/MANIFEST.MF
|  Main.class
|  A.class
|  B.class
|  lib/外部ライブラリ.jar
|  res/リソースファイル

前提条件

  • 配布先PCには、JREが事前にインストールされているものとする。
  • ダブルクリックで実行可能なJARファイルはEclipseを使用すれば簡単に作成できるが、
    今回は仕組みを理解するため、標準のJDKツールのみを使用する。
  • 開発環境/実行環境は「4.1 実習環境」を参照のこと。

調査

JARファイルの関連付け

Windowsの場合、JARファイルをダブルクリックで実行するには、
JARファイルに「java -jar」または「javaw -jar」コマンドを関連付け、
ダブルクリック時に自動的に「java(w) -jar XXX.jar」として実行する必要がある。
ただし、この関連付けはJREのインストール時に自動的に行われるため、
ユーザが行う必要はない。

以下、JARファイルのリファレンスガイドより引用。
Microsoft Windows システムでは、Java 2 Runtime Environment のインストールプログラムが 
JAR ファイルのデフォルトの対応付けを登録します。 その結果、デスクトップ上で JAR ファイルを
ダブルクリックすれば javaw -jar により JAR ファイルが自動的に実行されます。アプリケーション
にバンドルされた、アプリケーションが依存している拡張機能も自動的にロードされます。
JAR ファイルの概要 実行可能 JAR ファイル)
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/jar/jarGuide.html


Solarisの場合、デフォルトでJARファイルに対応しており、JREもインストール済みで
あるため、何の設定も行わずにダブルクリックでの実行が可能となる。

以下、JARファイルのリファレンスガイドより引用。
Solaris 2.6 のカーネルは、JAR ファイルを表す特別な「マジック」ナンバーを認識するように
すでに拡張が施されており、java -jar で JAR ファイルを Solaris のネイティブ実行可能ファイル
であるかのように起動できるようになっています。そのため、JAR ファイルにまとめられた
アプリケーションは、コマンド行から直接実行することも、CDE デスクトップ上でアイコンを
クリックして実行することもできます。
JAR ファイルの概要 (実行可能 JAR ファイル)
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/jar/jarGuide.html


WindowsとSolaris以外のOSについては、JARファイルのリファレンスガイドには
特別な記述は存在しなかった。
しかし、独自に下記OSについて、設定なしでJARファイルをダブルクリック
するだけで実行できるかを調査し、結果をまとめた。
 OS ダブルクリック実行 備考
 Mac OS X 10.6.2 (Snow Leopard) 可能 設定なしで実行可能。
 CentOS 5.3 不可能 書庫マネージャに関連付けられている 。
 手動で java -jar に関連付ける必要あり。
 Ubuntu Linux 8.04 (Hardy Heron) 不可能  同上
 StormOS Hail Beta 2 不可能  同上 


今回は「配布先PCでは外部ライブラリのインストールやシステムの設定等を
全く行わなくても良いようにする。」ことを目標の1つとしている。
この目標を達成するには、手動でJARファイルの関連付けを行う必要がないOSを
使用することが重要になる。
そのため、OpenSolaris 2009.06」を配布先PCのOSとして選択した。


外部ライブラリの選択

外部ライブラリは自作のものを使用する。
SWT等のライブラリを使用することも考えたが、
使用方法を調べる手間を省くため選択肢から除外した。

自作外部ライブラリは下記の通りシンプルな仕様とする。
  • ファイル名はlogging.jar
  • パッケージ名はSM.log
  • 引数に指定された文字列に日付と時刻を加えてファイルに出力する
この自作外部ライブラリを使用すると、
目標とするJARファイルの内部構造イメージは下記の通りとなる。

test.jar
| META-INF/MANIFEST.MF
| Main.class
| A.class
| B.class
| lib/logging.jar    (外部ライブラリ)
| res/リソースファイル


すべてを含んだJARファイルの作成方法

外部ライブラリ、リソースファイルを含んだJARファイルの作成方法を
下記環境を想定して説明する。


testディレクトリにJavaソースファイル、外部ライブラリ、
リソースファイルが存在する。
test
|  Main.java
|  A.java
|  B.java
|  lib/logging.jar
|  res/sakana.jpg



以下、コンソールでの作業。
  1. testディレクトリに移動する。
    $ cd /path/to/test

  2. ソースコードをコンパイルする。
    $ javac -classpath lib/logging.jar *.java

  3. マニフェストファイルを作成する。
    testディレクトリ内にtest.mfを作成する。
    詳細は「マニフェストファイル」を参照。
    $ vi test.mf

  4. JARファイルを作成する。
    $ jar cfm test.jar test.mf *.class lib/logging.jar res/sakana.jpg

  5. JARファイルの作成完了。

  6. JARファイルの内容を確認する。
    META-INF/MANIFEST.MFはjarコマンドが自動的に作成したものである。
    $ jar tf test.jar
    META-INF
    METAINF/MANIFEST.MF
    Main.class
    A.class
    B.class
    lib/logging.jar
    res/sakana.jpg

注意:    外部ライブラリ、リソースファイルを含んだJARファイルは
            作成できたが、このままでは動作しない。
            理由は「外部ライブラリを含む場合の問題」を参照。


javac、jarコマンドの詳細は下記ウェブサイトを参照。
javac - Java プログラミング言語コンパイラ[Windows]
http://java.sun.com/javase/ja/6/docs/ja/technotes/tools/windows/javac.html

javac - Java プログラミング言語コンパイラ[Solaris および Linux]
http://java.sun.com/javase/ja/6/docs/ja/technotes/tools/solaris/javac.html

jar - Java ARchive ツール [Windows]
http://java.sun.com/javase/ja/6/docs/ja/technotes/tools/windows/jar.html

jar - Java ARchive ツール [Solaris および Linux]
http://java.sun.com/javase/ja/6/docs/ja/technotes/tools/solaris/jar.html


マニフェストファイル

JavaアプリケーションのJARファイルを作成する場合、
public static void mainメソッドを含むクラス(以降、メインクラスと呼ぶ)や、
クラスパス等を指定する必要がある。
この指定はマニフェストファイルで行う。

必要最小限の指定項目として、メインクラス名とクラスパスが挙げられ、
それぞれ「Main-Class」属性と「Class-Path」属性で指定することができる。

下記環境を想定したマニフェストファイルの作成方法を説明する。
sample
|  Main.class
|  A.class
|  B.class

作成方法
  1. テキストエディタで適当な名前のファイルを作成する。
    $ vi sample.mf

  2. sample.mfに属性と値を保存する。内容は下記の通り。
    Main-Class: Main
    Class-Path: .

  3. JARファイルを作成する。
    $ jar cfm sample.jar sample.mf *.class

  4. JARファイルの内容を確認する。
    sample.mfを元にMETA-INF/MANIFEST.MFが
    自動的に作成されている。
    $ jar tf sample.jar
    META-INF
    METAINF/MANIFEST.MF
    Main.class
    A.class
    B.class

  5. META-INF/MANIFEST.MFの内容を確認する。
    赤字部分にsample.mfに記載した内容が反映されている事が分かる。
    赤字部分以外はjarコマンドによって自動的に追加された内容である。
    $ jar xf sample.jar
    $ cat META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Class-Path: .
    Created-By: 1.6.0_17 (Apple Inc.)
    Main-Class: Main



マニフェストファイルの詳細は下記ウェブサイトを参照。



外部ライブラリを含む場合の問題

JavaアプリケーションのJARファイルに、JARファイル形式の外部ライブラリを
含めると、外部ライブラリへのクラスパスが通らない。
つまり、今回作成しようとしているtest.jarの場合、Main.class、A.class、
B.classから、logging.jar内のクラスが参照できないという問題が発生する。
test.jar
|  META-INF/MANIFEST.MF
|  Main.class
|  A.class
|  B.class
|  lib/logging.jar  (参照できない)
|  res/sakana.jpg

これは、マニフェストファイル等に、JARファイル内のJARファイルを
クラスパスに指定する方法が存在しないことが原因である。


The Java Tutorialsによれば、この問題を解決するにはカスタムコード
を書く必要がある。
以下、The Java Tutorialsより引用。
To load classes in JAR files within a JAR file into the class path, you must write 
custom code to load those classes. For example, if MyJar.jar contains another
JAR file called MyUtils.jar, you cannot use the Class-Path header in MyJar.jar's
manifest to load classes in MyUtils.jar into the class path.


カスタムコードを書く以外に、下記方法が考えられるが
いずれも問題が残るため採用しない。
  • 外部ライブラリをJavaアプリケーションのJARファイルの外に配置する方法。
JARファイルの内部構造イメージはこのようになる。
test.jar
|  META-INF/MANIFEST.MF
|  Main.class
|  A.class
|  B.class
|  res/sakana.jpg

logging.jar
これで外部ライブラリへのクラスパスが通るが、
「配布物はJARファイル1つだけにする」という目標が達成できなくなる問題が残る。

  • 外部ライブラリのJARファイルを展開し、そのclassファイルを
    JavaアプリケーションのJARファイルに含める方法。
JARファイルの内部構造イメージはこのようになる。
test.jar
|  META-INF/MANIFEST.MF
|  Main.class
|  A.class
|  B.class
|  lib/SM/log/Logger.class
|  lib/SM/log/Message.class
|  res/sakana.jpg
これで外部ライブラリへのクラスパスが通り、
「配布物はJARファイル1つだけにする」という目標も達成できる。
しかし、外部ライブラリのライセンスがこのような配布方法を認めていない場合、
ライセンス違反をしない限り実行不可能であるという問題が残る。


外部ライブラリ問題の解決

上記「外部ライブラリを含む場合の問題」はEclipseが解決している。
その方法とは、The Java Tutorialsに記載してある通りカスタムコードを書くというものである。

詳細はEclipse 3.5でJARファイルを作成しその内容を確認しながら説明する。
  • JARファイルを作成する。
    1. プロジェクトのビルドパスに外部ライブラリを指定する。


    2. プロジェクトにリソースファイルを追加する。

    3. Javaアプリケーションのソースコードを書く。

    4. プロジェクトを Runnable JAR file としてエクスポートする。


    5. 次の画面で [Package required libraries into generated JAR] を選択する。


  • JARファイルの内容を確認する。
  1. 作成したJARファイルを展開する。
    $ jar xf test.jar

  2. 展開後のファイルの内容を確認する。
    ./
    |  META-INF/MANIFEST.MF
    |  Main.class
    |  B.class
    |  A.class
    |  logging.jar
    |  res/sakana.jpg
    |  org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader$ManifestInfo.class
    |  org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader.class
    |  org/eclipse/jdt/internal/jarinjarloader/RsrcURLConnection.class
    |  org/eclipse/jdt/internal/jarinjarloader/RsrcURLStreamHandler.class
    |  org/eclipse/jdt/internal/jarinjarloader/RsrcURLStreamHandlerFactory.class

  3. Eclipseで外部ライブラリをサブディレクトリに配置する方法が
    分からなかったため、logging.jarがtest.jar直下に配置されているが、
    この章の調査には外部ライブラリの配置場所は影響しないため問題ない。

    注目すべき点は、org/eclipse/idt/internal/jarinjarloaderに様々な
    classファイルが作成されていることである。
    これは、Eclipseが自動的に作成したもので、「jarinjarloader」という
    ディレクトリ名から推測できるように、JARファイル内のJARファイルを
    ロードするためのカスタムコードが書かれている。

    これらのカスタムコードは、いつ実行されるのか。
    答えはマニフェストファイルに書かれている。
    META-INF/MANIFEST.MFの内容を確認すると、JARファイル実行時に
    最初に実行されるのはJarRsrcLoaderクラスであることが分かる。
    Manifest-Version: 1.0
    Rsrc-Class-Path: ./ logging.jar
    Class-Path: .
    Rsrc-Main-Class: Main
    Main-Class: org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader
    さらに、Rsrc-ClassやRsrc-Main-Classという属性が自動的に追加されている。
    この2つは標準のマニフェストファイルの仕様には定義されていない属性である。

    これら(JarRsrcLoader、Rsrc-Class、Rsrc-Main-Class)の関係を
    理解するには、JarRsrcLoaderのソースコードを読む必要がある。
    [Eclipse_Project] View of /org.eclipse.jdt.ui/jar in jar loader/org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader.java
    http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.jdt.ui/jar%20in%20jar%20loader/org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader.java?revision=1.1&view=markup
    ソースコードを要約すると、
    「Rsrc-Class-Path属性に指定されている値でクラスパスを上書きすることにより
    外部ライブラリをロード可能にし、その上でRsrc-Main-Class属性に指定されて
    いるクラスを呼び出す」という内容になっている。

    これをtest.jarに当てはめると下記のようになる。
    (1) test.jarがダブルクリックで実行される。
    (2) JarRsrcLoaderクラスが実行される。
    (3) クラスパスを「./」と「logging.jar」で上書きする。
    (4) logging.jarがロード可能になる。
    (5) Mainクラス(Javaアプリケーション本体)が実行される。


リソースファイルへのアクセス

JARファイル内のリソースファイルへアクセスする方法を説明する。

下記JARファイルの内部構造イメージでは、
リソースファイルはres/sakana.jpgである。
sample.jar
|  META-INF/MANIFEST.MF
|  Main.class
|  A.class
|  B.class
|  res/sakana.jpg


Main.classからres/sakana.jpgを処理する場合、
単にパスを "res/sakana.jpg" と記述してもアクセスできない。
例えば、FileInputStream("res/sakana.jpg")とした場合、
sample.jarと同じディレクトリにあるres/sakana.jpgにアクセスしようとし、
sample.jar内のものにはアクセスできない。
sample.jar
|  META-INF/MANIFEST.MF
|  Main.class    (FileInputStream("res/sakana.jpg"))
|  A.class
|  B.class
|  res/sakana.jpg    (このファイルにはアクセスできない)

res/sakana.jpg    (このファイルにアクセスしようとする)


sample.jar内のリソースファイルにアクセスするには、ClassLoaderクラスの
getResourceまたは、getResourceAsStreamメソッドを使用する必要がある。
具体的にはMain.javaに下記ソースコードを書く必要がある。
java.net.URL url =
this.getClass().getClassLoader().getResource("res/sakana.jpg");

または

InputStream in = 

this.getClass().getClassLoader().getResourceAsStream("res/sakana.jpg");
getResourceメソッドは、リソースファイルのパスを表すURLを返し、
返り値の内容をtoStringメソッドで確認すると
「jar:file:/path/to/sample.jar!/res/sakana.jpg」となっている。
getResourceAsStreamメソッドは、リソースファイルのInputStreamを返す。

これらのメソッドを実行した後は、BufferedInputStreamクラスを使用し、
リソースファイルのコピー等を行うことができる。
InputStream in = 
this.getClass().getClassLoader().getResourceAsStream("res/sakana.jpg");

BufferedInputStream bufin = new BufferedInputStream(in);

while (bufin.read(data) > -1) { ... }


詳細は下記ウェブサイトを参照。
位置に依存しない方法でのリソースへのアクセス
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/lang/resources.html


実習

Javaアプリケーションの開発、配布、実行までの作業を行う。

実習環境


配布先PCを想定した環境(実行環境)を仮想マシン上に用意する。
なお、環境の構築手順は説明しない。

開発環境
 マシン MacBook Pro
 OS Mac OS X 10.6.2 (Snow Leopard)
 JDK Java SE JDK version 1.6.0_17
 JRE JDKに含まれるJREを使用
 外部ライブラリ 自作外部ライブラリ(logging.jar)

実行環境
 仮想マシン VirtualBox 3.0.12
 ゲストOS OpenSolaris 2009.06
 JDK 未インストール
 JRE Java SE JRE version 1.6.0_13
 外部ライブラリ 未インストール

仕様

Javaアプリケーションの仕様
  • パッケージはデフォルトパッケージとする。
  • JARファイル名はtest.jarとする。
  • test.jarの内部構造イメージは下記の通り。
    test.jar
    |  META-INF/MANIFEST.MF
    |  Main.class

    |  A.class
    |  B.class
    |  lib/logging.jar
    |  res/sakana.jpg
    |  org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader$ManifestInfo.class
    |  org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader.class
    |  org/eclipse/jdt/internal/jarinjarloader/RsrcURLConnection.class
    |  org/eclipse/jdt/internal/jarinjarloader/RsrcURLStreamHandler.class
    |  org/eclipse/jdt/internal/jarinjarloader/RsrcURLStreamHandlerFactory.class
  • JARファイル内のJARファイルをロードするには、カスタムコードを書く必要があるが
    今回はEclipseのjar-in-jar-loaderを流用する。
  • res/sakana.jpgを/tmp/fish.jpgとしてコピーする。
  • コピーの際、外部ライブラリを使用しログを出力する。

自作外部ライブラリの仕様
  • パッケージはSM.logとする。
  • JARファイル名はlogging.jarとする。
  • 引数に指定された文字列に日付と時刻を加えて/tmp/test.logに出力する。

開発

この章はすべて開発環境での作業となる。
作成したソースコード、リソースファイル等は「ダウンロード」から一括でダウンロードできる。

    1. 作業用ディレクトリを作成する。
      $ mkdir test
      $ cd test


    2. リソースファイル用のディレクトリを作成する。
      $ mkdir res


    3. 絵を描き、res/sakana.jpgとして保存する。


    4. 外部ライブラリのパッケージ用ディレクトリを作成する。
      $ mkdir -p lib/SM/log
      $ cd lib


    5. 外部ライブラリのソースコードを書く。
      $ vi SM/log/Logger.java
      $ vi SM/log/Message.java


    6. コンパイルする。
      $ javac SM/log/*.java


    7. JARファイルを作成する。
      $ jar cf logging.jar SM/log/*.class 
      $ cd ../


    8. Javaアプリケーションのソースコードを書く。
      $ vi Main.java
      $ vi A.java
      $ vi B.java


    9. コンパイルする。
      $ javac -cp lib/logging.jar *.java


    10. jar-in-jar-loaderをダウンロードする。


    11. jar-in-jar-loader.zipを展開する。
      $ unzip jar-in-jar-loader.zip


    12. マニフェストファイルを作成する。
      $ vi test.mf


    13. JARファイルを作成する。
      $ jar cfm test.jar test.mf *.class lib/logging.jar res/sakana.jpg org/


    14. test.jar完成。

    15. test.jarを別ディレクトリにコピーする。
      $ mkdir ~/hoge/sakana
      $ cp test.jar ~/hoge/sakana/


    16. コピーしたtest.jarをダブルクリックする。


    17. /tmp/fish.jpgが作成されていることを確認する。

    18. /tmp/test.logが作成されていることを確認する。

    19. 開発完了。


配布

  1. 開発環境で作成したtest.jarを、実行環境でダウンロードする。

  2. 配布完了


実行

この章はすべて実行環境での作業となる。

  1. 実行環境で、ダウンロードしたtest.jarをダブルクリックする。

  2. /tmp/fish.jpgが作成されていることを確認する。


  3. /tmp/test.logが作成されていることを確認する。


  4. 実行完了。
    配布先PCでJARファイルをダブルクリックしただけで実行できた。

まとめ

目標」で設定した目標はすべて達成できた。

重要なポイントは下記の通り。
  • JavaアプリケーションのJARファイルに外部ライブラリを含める場合は
    Javaアプリケーションのソースコードの他に、外部ライブラリをロード
    するためのカスタムコードを書く必要がある。
  • JARファイル内のリソースファイルのパスを取得するにはClassLoaderクラスの
    getResourceまたは、getResourceAsStreamメソッドを使用する必要がある。

ダウンロード

JARファイル:    test_RunnableJAR.zip
ソースコード:    test.zip


参考文献

One-JARでアプリケーションの配布を単純化
http://www.ibm.com/developerworks/jp/java/library/j-onejar/index.html

Č
ċ
ď
test.zip
(22k)
old sm,
2010/06/11 18:43
ċ
ď
test_RunnableJAR.zip
(20k)
old sm,
2010/06/11 18:44