Ideas - O'Reilly Media
訳すとかではなくて、これを読んで知識を整理してみた。
どのクラスにもpublic static final でclassというjava.lang.Class型のフィールドが出来る(MyClass.classみたいにアクセスする)。ただしjava.lang.Objectに定義されているわけではない。
クラスのアイデンティティは「クラス名」「パッケージ」「そのクラスをロードしたクラスローダ」の3つで決まる。つまり異なるクラスローダが「同じパッケージの同じクラスを同じclassファイルから」ロードした場合、それぞれが異なるクラスと認識される(キャストするとClassCastExceptionが起こる)。
普通のjavaプログラムを実行したときに使われるクラスローダは以下の3つ
- ブートストラップクラスローダ:JRE/lib以下の基本ライブラリを読む(java.langとか)。native実装。getClassLoader()してブートストラップクラスローダならnullが返る。
- ExtClassLoader:JRE/lib/ext以下(システムプロパティ"java.ext.dirs"で決まる)の拡張ライブラリを読む。
- AppClassLoader:クラスパスに通っているクラスを読む。システムクラスローダと呼ばれたりもする
クラスローダの親子関係はこのようになる(クラスの継承関係ではない)
ブートストラップクラスローダ
|
ExtClassLoader
|
AppClassLoader
クラスの継承関係はこのようになる。
ClassLoader
|
SecureClassLoader
|
URLClassLoader
|
AppClassLoader(ExtClassLoaderも同じ)
クラスローダはjava.lang.ClassLoaderを継承する必要がある。URLで指定するリソースからロードするならば、とりあえずURLClassLoaderを継承すればよさそう(JBossのUnifiedClassLoaderもそうしている)。
クラスローダは親クラスローダを持つ(ブートストラップクラスローダ以外)。デフォルトではクラスローダ(のクラス自体)をロードしたクラスローダが親になる。親クラスローダを明示的に指定する場合、クラスローダを引数にするsuperのコンストラクタを呼ぶ。
public MyClassLoader() { super(MyClassLoader.class.getClassLoader); }
クラスローダはloadClass()時に、自分がそのクラスを知らなければ親に委譲する。
ブートストラップクラスローダまで行き着いても誰も知らなければ、ブートストラップクラスローダが自分の守備範囲(基本ライブラリ)からクラスをfindClass()する。見つかれば返し、見つからなければClassNotFoundExceptionを投げる。親からClassNotFoundExceptionが飛んできたらcatchし、自分で自分の守備範囲からfindClass()する。この繰り返しで最後まで見つからなければアプリにClassNotFoundExceptionがきて終了。
ただしこれはjava.lang.ClassLoaderのloadClass()をベースにした流れなので、自分でオーバーライドしたら全く違う動きに出来る。
ClassLoaderのloadClass():
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
java.lang.ClassLoaderではfindClass()はClassNotFoundExceptionを投げるだけ。実装クラス(たとえばURLClassLoader)ではfindClass()でリソースを見つけ、それをもとにdefineClass()する。
CL1がMyClassをロードするためloadClass()を呼び出し、委譲されたCL2がクラスをfind、defineした場合、MyClassのクラスローダ(getClassLoader()の結果)は当然ながらCL2のほうである。nativeのdefineClass0()あたりでセットされるのだろうか。
「Internals of Java Class Loading」ではこのあとカスタムクラスローダのサンプルになるが、要は2つのクラスローダインスタンスを使ったから、同じクラス名でも2種類の実装をロードできた、という話。
さらに調べること:
- Class.forName()でなく、実際にクラスが実行されるときのクラスロード。たぶんresolveClass()や、Threadのコンテキストクラスローダについて
- アプリケーションサーバのクラスローダ。Tomcat、JBoss、WebLogicとか