torutkのブログ

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

Javaでプログラム実行後にJARファイルをクラスパスに追加したい

JavaJava SE)で、プログラム起動時にクラスパスでJARファイルを指定するのではなく、実行後にJARファイルを指定してクラスパスに追加したいことがあります。C/C++では、動的リンクライブラリをシステムコール(Win32 APIならLoadLibrary、UNIX系ならdlopen)で利用することができます。

Javaでは、クラスローダー(例えばjava.net.URLClassLoader)のインスタンスを作ってロードさせるのが順当な手段ですが、クラスローダーが異なる場合、staticの扱いが同じクラスローダーでロードした場合と異なるので、できればアプリケーションをロードしたシステムクラスローダーでロードさせたいところです。(staticに依存しないようアプリケーションが作られていればよいのですが、そうもいかないことが多いため)

Java SEのプログラムを実行するときは、以下のクラスローダーが動きます。

  1. ブートストラップ・クラスローダー
  2. インストールド・エクステンション・クラスローダー
  3. システム・クラスローダー

ブートストラップ・クラスローダーは、Javaのコア・クラス群(rt.jarなどに含まれるJava SE標準クラスライブラリ)をロードするために使われます。

インストールド・エクステンション・クラスローダーは、所定のインストールド・エクステンション・ディレクトリに置かれたJARファイルに含まれるクラスをロードします。所定のディレクトリとは、Java SE 6 JDKの場合、/jre/lib/ext です。なお、実行時にシステム・プロパティjava.ext.dirsで所定以外のディレクトリを(複数)指定することができます。

システム・クラスローダーは、クラスパス(環境変数CLASSPATHまたはコマンドラインオプションのclasspath)で指定されたパスにあるクラス、ならびにJARファイルに含まれるクラスをロードします。

インストールド・エクステンション・ディレクトリに置いたクラスから、システム・クラスローダーがロードするクラスを見ることができないので、アプリケーションに対する追加機能を後付で提供するような用途では、システム・クラスローダーにクラスをロードさせる必要があります。

システム・クラスローダーをURLClassLoaderにキャストして操る

に、システム・クラスローダー(sun.misc.Launcher.AppClassLoader)がURLClassLoaderのサブクラスであることを利用し、URLClassLoaderにキャスト、さらにprotectedメソッドであるaddURLにアクセスするため、リフレクションを使うという技が紹介されています。

URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();

まず、システム・クラスローダーをURLClassLoaderにキャストして*1、次に、

Class sysclass = URLClassLoader.class;

とURLClassLoaderのクラス型インスタンスを取得しておいてから、

try {
    Method method = sysclass.getDeclaredMethod("addURL", new Class[]{URL.class});
    method.setAccessible(true);
    method.invoke(sysloader, new Object[]{fooFile.toURI().toURL()});
} catch ( 以下略

リフレクションでURLClassLoaderのaddURLメソッドを取得し、アクセスを許可してから、JARファイルのURLを指定してメソッドを実行します。

なるほど〜。

注意点

JARファイルのロードを記述するクラスではJARファイル内のクラスを見ない

上記のJARファイルのロードを記述したクラス(クラスA)で、JARファイルに含まれるクラス(クラスB)を使用していると、クラスAを実行する(クラスAをJavaVMにロード・リンクする)段階でクラスBをロードしようとしてNoClassDefFoundErrorが発生することがあります。

当たり前のことですが、ついやってしまうことがあります。JARファイル内のクラス(クラスB)にアクセスするクラス(クラスC)を作成し、クラスAでJARファイルのロード処理後、クラスAからクラスCを呼び、クラスCからクラスBを使用するようにします。

*1:SunのJavaVM実装依存なので、この部分はクロスプラットフォーム安全ではない