Javaクラスローダーを自作する
クラスローダーを自作するメリットですが、あまり思いつきませんでした。
- 特殊な方法でアプリケーションを配布したい。
- 特殊な方法で試験をしたい。
- 2年くらい前に自作したわー感を出す。
クラスローダーとは
クラスローダーは、クラスのロードを担当するオブジェクトです。ClassLoaderクラスは abstract クラスです。クラスのバイナリ名を指定すると、クラスローダーはクラスの定義を構成するデータを見つけるか生成します。一般的な方法としては、名前をファイル名に変換して、システムからその名前の「クラスファイル」を読み込みます。
http://docs.oracle.com/javase/jp/6/api/java/lang/ClassLoader.htmlより
Class.forName("oracle.jdbc.driver.OracleDriver");
よく見かけるこの記述は、oracle.jdbc.driver.OracleDriverというクラスを、現在のクラスを定義するクラスローダーを使ってロードしています。
簡単なクラスローダーを作る
指定したディレクトリをクラスパスに追加するクラスローダーを作ります。
public static ClassLoader createClassLoader(String dirname) throws java.io.IOException {
java.net.URL[] url = new java.net.URL[1];
java.io.File file;
if (dirname.endsWith("/")) {
file = new java.io.File(dirname);
}
else {
// ディレクトリは最後にスラッシュが必要
file = new java.io.File(dirname + "/");
}
url[0]= file.toURI().toURL();
ClassLoader parent = ClassLoader.getSystemClassLoader();
java.net.URLClassLoader loader = new java.net.URLClassLoader(url, parent);
return loader;
}
/*-
* 使い方
* classディレクトリ配下にある test.Main クラスの main メソッドを実行する。
*/
private void foo() throws Exception {
ClassLoader loader = createClassLoader("./class/");
Class<?> cls = Class.forName("test.Main", true, loader);
java.lang.reflect.Method method = cls.getMethod("main", new Class[]{String[].class});
method.invoke(null, new Object[]{null});
}
今回はディレクトリ版でしたが、jarファイルやzipファイルもURLClassLoaderを使うことで簡単にクラスを読み込むことができます。
これで何がうれしいでしょうか?
特殊な方法でアプリケーションを配布したい
例えば、あるアプリがプラグインで拡張できる形式だったとして、プラグインがネットから自動で落とせるようになっていたら、起動時にクラスパスにないクラスを実行時に追加することができます。
例えば、社員全員が使うようなアプリの場合、メインモジュールをこの形式で実行するようにしておき、起動時にサーバに問い合わせして更新があるかを確認するようにしておけば、そのアプリをバージョンアップする場合やバグフィックスする場合にいちいち全員にファイルを配らなくても良いようになります。
特殊な方法で試験をしたい
モック差し替えのようなことが実現できますが、今は優れたモックがいろいろとあるので、割愛します。
2年くらい前に自作したわー感を出す
…
もうちょっとちゃんとしたクラスローダー
バイト配列を元にクラスを生成するクラスローダーです。
手を抜くため、どこかからクラス名とclass のバイト配列を事前に登録しておき、必要に応じて読み込ませるという方式にしています。
public class MyOwnClassLoader extends ClassLoader {
private java.util.Map<String, byte[]> classMap;
public MyOwnClassLoader() {
classMap = new java.util.HashMap<>();
}
public void addClass(String className, byte[] b) {
classMap.put(className, b);
}
@Override
protected Class findClass(String name) throws ClassNotFoundException {
byte[] b = classMap.get(name);
if (b == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, b, 0, b.length);
}
}
制限
Java標準クラスおよびCLASSPATHにあるクラスは起動時に読み込まれているので、この方法でこれらのクラスを置き換えることはできません。