クラスローダを自作する
なぜ自前のクラスローダが必要なのか?
クラスローダによって検索するディレクトリと名前空間を分けることができる。よってクラスローダを分ける以下の2点を実現できる。
利用可能なクラスを制限する
- サービスAからはサービスBのクラスを利用できないようにする
- サービスAからシステムが利用している基幹クラスを利用できないようにする
お互い利用するクラスの衝突を避けることができる。
- サービスAがlibrary.classのv1、サービスBがlibrary.classのv2を使ってもお互い問題なく利用できる
どんなときに利用するの?
どうやるの?
- サービスAのJARファイルをclasspath以外の場所に配置する
- java.net.URLClassLoaderのインスタンスを作成する
- URLClassLoader#addURLでクラスやJARファイルへのパスを追加する
- URLClassLoaderインスタンスを利用してサービスAクラスをロードする
Class clazz = Class.forName("com.example.ServiceA", true, myClassLoader); Class clazz = myClassLoader.loadClass("com.example.ServiceA");
- サービスAクラスをインスタンス化する(リフレクションなど)
obj = clazz.newInstance();
独自の動作をさせたいとき
クラスローダがクラスをロードするときにはClassLoader#loadClassが呼ばれる。ClassLoader#loadClassはまず親クラスローダに処理を委譲する(parent.loadClass())。親クラスローダがクラスを見つけられなかった場合に自身でクラスを検索する(ClassLoader#findClass)。
この処理順序を変えたい場合はURLClassLoaderを拡張したクラスを作成してloadClassをオーバーライドする。例えばTomcatのように最初は自身で検索を実行して見つからなかった場合に親クラスローダに委譲するようなケースだ。
しかしClassLoader#loadClassのコメントを見るとloadClassメソッドのオーバーライドは推奨されていない。オーバーライドするのはClassLoader#findClassに留めておくようにとのことだ。順序を変えることによるクラスの衝突や、インスタンスのGargage Collectionが適切に行われなくなる危険性があることが推奨しない理由かと思われる。
コンテクストクラスローダ
スレッドが持つスレッド固有のクラスローダ。以下のようにして使う。
- "Thread#setContextClassLoader"で固有のクラスローダをそのスレッドに割り当てる
- "Thread#getContextClassLoader"でクラスローダを取り出してクラスをロードする
ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class clazz = cl.loadClass("com.sample.ServiceA");
そのスレッドのみが利用できるクラスをコンテキストクラスローダに管理させることができる。正直、使い所がわからない。