(補足) クラスローダ
1. クラスローダ
「クラスローダ」とは、クラスのロードやリソース(ファイル)の検索を担当するオブジェクトで、「java.lang.ClassLoader」を継承したクラスのインスタンスです。全てのクラスはクラスローダによってアプリケーションにロードされる事になっており、各クラスは自身をロードしたクラスローダへの参照を保持しています。そのクラスローダは「java.lang.Class」の「getClassLoader」メソッドで取得することができます。
クラスローダはツリー構造をしており、関連する親クラスローダを1つ所有しています。ツリー構造の大元に位置するクラスローダを「ブートストラップ・クラスローダ」と呼びます。これはJava仮想マシンに組み込まれており、Javaアプリケーションの起動時に、最初に読み込まれるものです。ブートストラップ・クラスローダはJava標準のライブラリや、Java仮想マシンの拡張ディレクトリ「jre/lib/ext」に置かれたjarファイルにあるクラスしかロードできません。
「ブートストラップ・クラスローダ」の次に位置しているのが、「システム・クラスローダ」です。システム・クラスローダはブートストラップ・クラスローダの唯一の子クラスローダです。システム・クラスローダは、CLASSPATHからクラスのロードやリソースの検索を行うもので、Javaアプリケーションを実行する上で重要な役割を果たすものです。また独自に作成・設定したクラスローダは、何も指定しなければシステム・クラスローダを親として持ちます。システム・クラスローダは、もっとも基本的なクラスローダといえます。
クラスローダの親子関係は、クラスをロードする上で重要な働きをします。デフォルトでは、子クラスローダがクラスをロードする際、まず親クラスローダに処理を依頼(委譲)します。親クラスローダで検索できなかった場合に、はじめて子クラスローダで検索を試みます。全てのクラスローダはシステム・クラスローダを親(先祖)に持っていますので、CLASSPATHにあるクラスは、どこからも検索できることになります。
ところでアプリケーションはメインスレッドも含めて、必ず1つ「コンテクスト・クラスローダ」を持ちます。コンテキスト・クラスローダとは、そのスレッド内でクラスをロードする場合に使用されるクラスローダのことです。これを使用することにより、特定のスレッドでは、親スレッドで使用できないクラスをロードできるようになります。コンテキスト・クラスローダは、スレッドを生成する親スレッドの側で設定されます。もし設定されない場合には、親スレッドのコンテキスト・クラスローダが設定されます。コンテキスト・クラスローダの設定や取得は、「java.lang.Thread」の「setContextClassLoader」「getContextClassLoader」メソッドで行います。なお最初に起動されるメインスレッドのコンテキスト・クラスローダは、デフォルトでシステム・クラスローダになります。
(実習課題1)
以下のコンソール・アプリケーションを作成しなさい。
- 通常、クラスローダは「java.net.URLClassLoader」を継承して作成されます。JDKのシステム・クラスローダ/ブートストラップ・クラスローダも、URLClassLoaderを継承して作成されています。
- URLClassLoaderの「getURLs」メソッドを使用し、システム・クラスローダ/ブートストラップ・クラスローダが検索対象とするURLの一覧を表示するプログラムを作成しなさい。
CLASSPATHの設定を変更すると、システム・クラスローダが検索対象とするURLも変更されることを確認すること。
2. Tomcatのクラスローダ
Tomcatは複数のスレッドによって構成される、複雑なJavaアプリケーションです。クラスローダも下図のように、複数設定されています。
Tomcatでは、各Webアプリケーション毎にクラスローダが設定されています。クラスローダが検索の対象とするのは、「WEB-INF/classes」以下にあるクラスファイルと、「WEB-INF/lib」以下にあるjarファイルです。サーブレットが動作するスレッドのコンテキスト・クラスローダも、このクラスローダとなっています。
各Webアプリケーションのクラスローダの親は、「Sharedクラスローダ」です。SharedクラスローダはTomcatの「shared/classes」以下にあるクラスファイルと、「shared/lib」以下にあるjarファイルを検索対象とします。Sharedクラスローダは全Webアプリケーションで共有されているので、「shared/classes」「shared/lib」以下にあるクラス/リソースは、全てのWebアプリケーションで共通のものとなります。
「Catalinaクラスローダ」は、Tomcatの実行に使用されるクラスローダです。このクラスローダは全てのWebアプリケーションから見えません。したがってこのクラスローダでしかロードできないクラス/リソースは、Webアプリケーションから使用できないようになっています。
「Commonクラスローダ」は、CatalinaクラスローダおよびSharedクラスローダの親です。つまりTomcatと全Webアプリケーションで共通のクラスローダということになります。Commonクラスローダは、Tomcatの「common/lib」「common/classes」「common/endorsed」以下から検索を行います。Commonクラスローダの親はシステム・クラスローダです。
最後にクラスローダの委譲順について説明します。デフォルトでは、クラスを検索する際、親クラスローダから順に検索を行います。しかしTomcatでは多少、異なっています。Webアプリケーションでクラスを検索する際、以下の順番で行われます。
- Webアプリケーション自身のクラスローダ
- Commonクラスローダ
- Sharedクラスローダ
- システム・クラスローダ
- ブートストラップ・クラスローダ
3. Webアプリケーション間でクラスを共有する場合の注意点
Tomcatでは、「common」および「shared」以下においたクラスファイルやjarファイルは、全Webアプリケーションで共通に使用できます。しかしこれらのクラスには1つ注意しなければならない点があります。クラス変数についてです。
インスタンスは処理が実行されているスレッド内に作成されるため、共通のクラスであってもインスタンスそのものが共有されることはありません。しかしクラスに属するクラス変数は異なります。クラス変数はそのクラスをロードしたクラスローダに属するため、全Webアプリケーションで共有されます。
例えば以下のようなクラスをCommonクラスローダ(またはSharedクラスローダ)におきます。このクラスは一見すると、インスタンス単位でデータ管理をしている印象を与えますが、内部ではクラス単位で管理をしています。ですからあるWebアプリケーションで「setText」メソッドを使用すると、他のWebアプリケーションにもその結果が影響します。自身が作成したクラスであれば内部も解るので避けることが可能ですが、他で作成されたものを使用する場合には、かなりの注意が必要です。
package shared; public class Label{ private static String text=""; public synchronized void setText(String text){ this.text=text; } public String getText(){ return(text); } }
また同じクラスを、複数のクラスローダに配置した場合には、もっと注意が必要です。例えば「both.Sample」というクラスを、あるWebアプリケーションと、先ほどのLabelクラスと同じCommonクラスローダの2箇所に配置したとします。その上でLabelクラスに以下のメソッドを追加します。このメソッドの結果はどうなるでしょうか?
public boolean classTest() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class sampleClass; try { sampleClass = cl.loadClass("both.Sample"); return (sampleClass.equals(both.Sample.class)); } catch (ClassNotFoundException e) { e.printStackTrace(); } return false; }
「false」が返ります。いずれも「both.Sample」クラスを表すので、「true」が返ってもおかしくありません。しかし結果はそうなりません。理由はクラスローダの違いにあります。
2行目のgetContextClassLoaderはWebアプリケションのクラスローダを返します。ですから3行目の「both.Sample」クラスは、Webアプリケーションのクラスローダによってロードされた「both.Sample」クラスを指します。
対して4行目の「both.Sample.class」は、Commonクラスローダによってロードされた「both.Sample」クラスを指します。「class」キーワードによって返されるClassオブジェクトは、実行したクラス(shared.Label)をロードしたクラスローダ(Commonクラスローダ)によって、ロードできるクラスを指すからです。ClassのforNameメソッドも同じです。
このプログラムではエラーが発生しませんが、場合によってはエラーが発生してWebアプリケーションが実行しない可能性もあります。親子関係にあるクラスローダ同士では、同名の別クラスがロードされないように注意しましょう。
(実習課題2)
3節で示した、1つ目の問題が発生する事を確認しなさい。
(実習課題3)
3節で示した、2つ目の問題が発生する事を確認しなさい。