Revision 2 as of 2010-01-16 02:34:30

Clear message
Locked History Actions

java/DynamicClassPath

実行時にクラスパスを追加する

問題

一般的にJavaアプリケーションを構成するものは、そのアプリ自体の.classからなる.jarファイル(これを仮にapp.jarとする)だけではない。サードパーティ製の多数の.jarファイルが必要になる。アプリの実行時に、これらの.jarファイルがクラスパス上に存在しなければアプリケーションは動作できない。

クラスパスの指定方法として、一般的には以下のようなものがある。

  • 1.コマンドラインにて指定する方法

java -cp A.jar;B.jar;C.jar -jar app.jar
  • 2.jarファイルのマニフェスト内に記述する方法

Manifest-Version: 1.0
Main-Class: ....
Class-Path: ....
  • 3.サードパーティ製の.jarファイルをほぐしてしまい、.classの形にしてからapp.jarにまとめてしまう方法

いずれも問題がある。1.はいかにも面倒だし、jarファイルをダブルクリックした場合の起動には対応できない。2.はスタンダードな方法ではあるが、ライブラリの増減のある場合には面倒かもしれない。 3.は、サードパーティ製ライブラリがGPLライセンスの場合は問題である。ユーザ側で容易にライブラリの変更ができないからである。

もっと簡単な方法はないものか?例えば、「java -jar app.jar」あるいはダブルクリックして起動されると、特定のフォルダ(例えばapp.jarと同じフォルダ内のlibフォルダ)を探し、そこにあるすべての.jarファイルをクラスパスに追加してから起動するようなことができないものか?

※もちろん、libフォルダ内を探索するコード部分では、libフォルダ内の.jarファイルは使用しないものとする。

解決策の提案

解決策としては以下が考えられる。

  • システムクラスローダに無理矢理クラスパスを指定する。
  • クラスパス指定された新たなプロセスを起動する。
  • クラスパスを指定した新たなクラスローダを作成する。

システムクラスローダに無理矢理クラスパスを指定する

この方法は以下に記述されている。要するに、本来クラスローダは後からクラスパスを指定できるものではないのだが、リフレクションを使って無理矢理行うものである。これがうまく行くのかどうかは不明。

クラスパス指定された新たなプロセスを起動する

「java -jar app.jar」として起動されたプロセスにはクラスパス指定されていないのだから、「java -cp ... -jar app.jar」としてクラスパス指定された新たなプロセスを作成してしまえばよい。

※ただし、メインクラスとして同じものを指定しても同じ処理が続くだけであるので実際には 「java -cp ....;app.jar foo.bar.AnotherEntry」などとして異なるメインクラスを明示的に指定する。

    Process process;
    try {
      process = Runtime.getRuntime().exec(クラスパス指定をしたコマンド);
    } catch (IOException ex) {
      ex.printStackTrace();
      return;
    }   
    ...
    System.exit(0); // プロセス起動に成功したので、このプロセスは終了

この方法には注意点がある。意図通りにプロセス起動されたかどうかはProcessオブジェクトからはよくわからない。とりあえずJava-VMが起動すればエラーも出ずにProcessオブジェクトが返されてしまう。実際にはProcessの標準出力等を調査して適切な起動が行われたかを調査した後にSystem.exit()すべきかと思われる。

また、この方法には極めて良い面がある。VM引数を指定できるのである。VM引数は起動時のみ指定できるため、例えば起動する側が設定ファイルを読み込み、それをVM引数として指定することができる。これにより、あらゆるVM引数をコマンドライン上で指定する必要がなくなる。それらは設定ファイルに記述しておけばよいのである。

java -Xmx256M -D.... -cp ....;app.jar foo.bar.AnotherEntry

クラスパスを指定した新たなクラスローダを作成する