[java]ランチャーを作成してアプリケーションを起動する
2006/07/21
2006/11/26
クラスローダ
javaアプリケーションを起動する際に、注意しないといけないのは昔からクラスパスだと思う。
アプリケーションによってはオープンソースのライブラリなど多く使用する場合、起動時に依存するライブラリについて全て暮らすパスを用意しないといけない。
個人的に、JAVA_HOMEにあるlib/extにライブラリを置いとくということはすすめないです。
というのは、《なんとなく動く》ということを引き起こしてしまいがちだからです。
よくあるのがjavaコマンドを使用する際にクラスパスを設定する方法です。
この場合、環境変数を設定して、プラットフォームごとのスクリプト(sh,bat)を用意するの面倒になります。
というわけで、下記のシナリオを考えてみました。
シナリオ:
これは軽量HTTPサーバのjettyのバージョンが5のソースコードを調べていたことが着想となります。
最終的には以下のようなjarの構成になります。
launcher.jar
application.jar
dependency.jar
アプリケーションの動作環境は下記のようなディレクトリ構成になります。
HOME/
lib/*.jar
conf/設定ファイル
ここから
開発していてはまった問題です。技術的な説明は調べている途中です。
ランチャー部分のjarと、アプリケーション部分のjarを分離させること。
アプリケーション部分のjarが依存しているjarをみつけることができないケースが発生する。
(1)最初のクラスローダ(ランチャーjar、アプリケーションjar)
(2)オリジナルのクラスローダ(依存しているjar)
問題は(1)のクラスローダで読み込んだクラスからは(2)で読み込んだクラスがみつからないということです。
javaではクラスの呼び出しが発生したときにClassNotFoundが発生します。
ですので、依存しているjarのクラスを実際に使用する段階になってはじめてClassNotFoundが発生します。
クラスローダを重ねていく(委譲していく?)際の注意は、クラスの依存の方向を常に後から読み込んだものは先に読み込んだものに依存するようにする
ということだと現在は自分の中で結論づけています。
ですので、
(1)最初のクラスローダ(ランチャーjar)
(2)オリジナルのクラスローダ(アプリケーションjar、依存しているjar)
というにします。
ではランチャーjarはアプリケーションjarをどのようによびだすかといいますと、クラスローダのloadClassメソッドを使用してクラスをロードして、
リフレクションを使用してアプリケーションを起動します。
(2)オリジナルのクラスローダを設定するときは、
Thread.currentThread().setContextClassLoader(myClassLoader);.
jettyではアプリケーション部分の読み込みは、invokeMainという処理を用意しています。
public static void invokeMain(ClassLoader classloader,String classname,String[] args)
throws IllegalAccessException,InvocationTargetException,NoSuchMethodException,ClassNotFoundException
{
Class invoked_class=null;
invoked_class=classloader.loadClass(classname);
Class[] method_param_types=new Class[1];
method_param_types[0]=args.getClass();
Method main=null;
main=invoked_class.getDeclaredMethod("main",method_param_types);
Object[] method_params=new Object[1];
method_params[0]=args;
main.invoke(null,method_params);
}
クラスローダの振る舞いはじっくり調べてみると必要がありそうですね。
参考リンク:
One-JARでアプリケーションの配布を単純化する
試行錯誤
jettyのサイト
サンのサイト javadocクラスローダの説明っぽい
javaアプリケーションを起動する際に、注意しないといけないのは昔からクラスパスだと思う。
アプリケーションによってはオープンソースのライブラリなど多く使用する場合、起動時に依存するライブラリについて全て暮らすパスを用意しないといけない。
個人的に、JAVA_HOMEにあるlib/extにライブラリを置いとくということはすすめないです。
というのは、《なんとなく動く》ということを引き起こしてしまいがちだからです。
よくあるのがjavaコマンドを使用する際にクラスパスを設定する方法です。
この場合、環境変数を設定して、プラットフォームごとのスクリプト(sh,bat)を用意するの面倒になります。
というわけで、下記のシナリオを考えてみました。
シナリオ:
アプリケーションとアプリケーションをスタートさせるランチャー部分を分ける。
ランチャー部分には環境変数XXX_HOMEなどの設定や、任意のクラスパスを設定したクラスローダを準備させる。
ランチャーは単純にjava -jar launcher.jarで動作させる。
これは軽量HTTPサーバのjettyのバージョンが5のソースコードを調べていたことが着想となります。
最終的には以下のようなjarの構成になります。
launcher.jar
application.jar
dependency.jar
アプリケーションの動作環境は下記のようなディレクトリ構成になります。
HOME/
lib/*.jar
conf/設定ファイル
ここから
開発していてはまった問題です。技術的な説明は調べている途中です。
ランチャー部分のjarと、アプリケーション部分のjarを分離させること。
アプリケーション部分のjarが依存しているjarをみつけることができないケースが発生する。
(1)最初のクラスローダ(ランチャーjar、アプリケーションjar)
(2)オリジナルのクラスローダ(依存しているjar)
問題は(1)のクラスローダで読み込んだクラスからは(2)で読み込んだクラスがみつからないということです。
javaではクラスの呼び出しが発生したときにClassNotFoundが発生します。
ですので、依存しているjarのクラスを実際に使用する段階になってはじめてClassNotFoundが発生します。
クラスローダを重ねていく(委譲していく?)際の注意は、クラスの依存の方向を常に後から読み込んだものは先に読み込んだものに依存するようにする
ということだと現在は自分の中で結論づけています。
ですので、
(1)最初のクラスローダ(ランチャーjar)
(2)オリジナルのクラスローダ(アプリケーションjar、依存しているjar)
というにします。
ではランチャーjarはアプリケーションjarをどのようによびだすかといいますと、クラスローダのloadClassメソッドを使用してクラスをロードして、
リフレクションを使用してアプリケーションを起動します。
(2)オリジナルのクラスローダを設定するときは、
Thread.currentThread().setContextClassLoader(myClassLoader);.
jettyではアプリケーション部分の読み込みは、invokeMainという処理を用意しています。
public static void invokeMain(ClassLoader classloader,String classname,String[] args)
throws IllegalAccessException,InvocationTargetException,NoSuchMethodException,ClassNotFoundException
{
Class invoked_class=null;
invoked_class=classloader.loadClass(classname);
Class[] method_param_types=new Class[1];
method_param_types[0]=args.getClass();
Method main=null;
main=invoked_class.getDeclaredMethod("main",method_param_types);
Object[] method_params=new Object[1];
method_params[0]=args;
main.invoke(null,method_params);
}
クラスローダの振る舞いはじっくり調べてみると必要がありそうですね。
参考リンク:
One-JARでアプリケーションの配布を単純化する
試行錯誤
jettyのサイト
サンのサイト javadocクラスローダの説明っぽい
: