クラス・ローディング処理を細かく制御するための解決策は、カスタム・クラス・ローダーを実装することです。 すべてのカスタム・クラス・ローダーは、直接または遠隔のスーパークラスとしてjava.lang.ClassLoaderを持つ必要があります。 また、コンストラクタ内で親のクラス・ローダーを指定する必要があります。 さらに、findClass()メソッドをオーバーライドする必要があります。 differentversionspushフォルダには、FileSystemClassLoaderと呼ばれるカスタム・クラス・ローダーが含まれています。 図9に、その構造を示します。
図9. カスタム・クラス・ローダーの関係
次に、FileSystemClassLoaderに実装されているメイン・メソッドを示します。
public byte[] findClassBytes(String className){
try{
String pathName = currentRoot +
File.separatorChar + className.
replace('.', File.separatorChar)
+ ".class";
FileInputStream inFile = new
FileInputStream(pathName);
byte[] classBytes = new
byte[inFile.available()];
inFile.read(classBytes);
return classBytes;
}
catch (java.io.IOException ioEx){
return null;
}
}
public Class findClass(String name)throws
ClassNotFoundException{
byte[] classBytes = findClassBytes(name);
if (classBytes==null){
throw new ClassNotFoundException();
}
else{
return defineClass(name, classBytes,
0, classBytes.length);
}
}
public Class findClass(String name, byte[]
classBytes)throws ClassNotFoundException{
if (classBytes==null){
throw new ClassNotFoundException(
"(classBytes==null)");
}
else{
return defineClass(name, classBytes,
0, classBytes.length);
}
}
public void execute(String codeName,
byte[] code){
Class klass = null;
try{
klass = findClass(codeName, code);
TaskIntf task = (TaskIntf)
klass.newInstance();
task.execute();
}
catch(Exception exception){
exception.printStackTrace();
}
}
クライアントは、このクラスを使用して、client.TaskImpl(v1)をbyte[]へと変換します。 次に、このbyte[]がRMIサーバーの実行エンジンに送信されます。 サーバーでも同じクラスを使用して、byte[]形式のコードからクラスを定義します。 クライアント側のコードを次に示します。
public class Client{
public static void main (String[] args){
try{
byte[] code = getClassDefinition
("client.TaskImpl");
serverIntf.execute("client.TaskImpl",
code);
}
catch(RemoteException remoteException){
remoteException.printStackTrace();
}
}
private static byte[] getClassDefinition
(String codeName){
String userDir = System.getProperties().
getProperty("BytePath");
FileSystemClassLoader fscl1 = null;
try{
fscl1 = new FileSystemClassLoader
(userDir);
}
catch(FileNotFoundException
fileNotFoundException){
fileNotFoundException.printStackTrace();
}
return fscl1.findClassBytes(codeName);
}
}
実行エンジン内部では、クライアントから受け取ったコードがカスタム・クラス・ローダーに渡されます。 カスタム・クラス・ローダーはbyte[]からクラスを定義して、そのクラスをインスタンス化し、実行します。 ここで注目すべきは、それぞれのクライアント・リクエストに対して、別々のFileSystemClassLoaderクラス・インスタンスを使用して、クライアントから提供されるclient.TaskImplが定義されている点です。 また、client.TaskImplはサーバーのクラスパスからは見つかりません。 つまり、FileSystemClassLoaderでfindClass()を呼び出すと、findClass()メソッドは内部でdefineClass()を呼び出し、クラス・ローダーの特定のインスタンスによってclient.TaskImplクラスが定義されます。 したがって、FileSystemClassLoaderの新しいインスタンスが使用されると、byte[]からもう一度新たにクラスが定義されます。 このように、クライアントが起動されるたびに何度もclient.TaskImplクラスが繰り返し定義され、同じ実行エンジンのJVM内で"異なるバージョン"のclient.TaskImplコードを実行できるようになります。
public void execute(String codeName, byte[] code)throws RemoteException{
FileSystemClassLoader fileSystemClassLoader = null;
try{
fileSystemClassLoader = new FileSystemClassLoader();
fileSystemClassLoader.execute(codeName, code);
}
catch(Exception exception){
throw new RemoteException(exception.getMessage());
}
}
この例は、differentversionspushフォルダに含まれています。 サーバー側とクライアント側のコンソールを図10、11、12に示します。
図10. カスタム・クラス・ローダーの実行エンジン
図10は、カスタム・クラス・ローダーの実行エンジンのVMコンソールを示しています。 ここから、client.TaskImplコードが複数回ロードされていることが分かります。 実際、クライアントの実行コンテキストごとに、クラスが新しくロードされ、インスタンス化されます。
図11. カスタム・クラス・ローダーのエンジン、クライアント1
図11では、client.TaskImpl.class.getClassLoader(v1)というログ・ステートメントを含むTaskImplクラスのコードがクライアントVMによってロードされ、実行エンジン・サーバーに渡されています。 図12のクライアントVMでは、client.TaskImpl.class.getClassLoader(v2)というログ・ステートメントを含む、TaskImplクラスの別のコードがロードされ、サーバーVMに渡されています。
図12. カスタム・クラス・ローダーのエンジン、クライアント2
このコード例は、別々のクラス・ローダー・インスタンスを利用して、"異なるバージョン"のコードを同じVM内で並列実行する方法を示しています。
一部のJ2EEサーバーに含まれるクラス・ローダーは、異なる間隔でクラスを削除し、リロードします。 この現象が発生するのは一部の実装においてのみです。 同様に、Webサーバーでも、以前にロードされたサーブレット・インスタンスが削除される場合があります。これは、サーバー管理者による明示的な要求があった場合や、サーブレットが長時間アイドル状態にあった場合に発生します。 JSP(プリコンパイルされていないと想定)へのリクエストが最初に出された際、JSPエンジンはJSPを標準Javaサーブレットの形を取って、そのページ実装クラスに変換します。 このページの実装サーブレットが作成されると、サーブレットはJSPエンジンによってクラス・ファイルにコンパイルされ、いつでも使用できる状態になります。 コンテナはリクエストを受け取るたびに、JSPファイルが最後の変換以降に変更されているかどうかをまずチェックします。 変更されている場合、再変換を実行します。こうすることで、常に最新のJSPファイルの実装を使用してレスポンスが生成されるようになります。 また、エンタープライズ・アプリケーションのデプロイメント・ユニット(.ear、.war、.rar形式など)に対しても、任意のタイミングや構成済みのポリシーに従って、ロードとリロードが実行できなければなりません。 これらすべてのシナリオに対してロード、アンロード、リロードを実行できるのは、アプリケーション・サーバーのJVMのクラス・ローディング・ポリシーを制御できる場合に限られます。 これは、その境界に定義されたコードを実行できる拡張クラス・ローダーを使用することで達成できます。 J2EEアプリケーション・サーバーのコンテキストにおけるクラス・ローディング・スキーマについて、詳しくはBrett Petersonの記事『Understanding J2EE Application Server Class Loading Architectures』(TheServerSide.com)を参照してください。
この記事では、Java仮想マシンにロードされたクラスがどのようにして一意に識別されるか、また、名前とパッケージが同じでありながら異なるバイトコードをロードする際に、どのような制限があるかについて説明しました。 クラスをバージョニングするための明確なメカニズムは存在しないため、思いどおりにクラスをロードするには、拡張機能を備えたカスタム・クラス・ローダーを使用する必要があります。 多くのJ2EEアプリケーション・サーバーでは"ホット・デプロイメント"機能が提供されており、サーバーVMを停止することなく、新しいバージョンのクラス定義を持つアプリケーションをリロードできます。 このようなアプリケーション・サーバーでは、カスタム・クラス・ローダーが役立ちます。 アプリケーション・サーバーを使用しない場合でも、カスタム・クラス・ローダーを作成して使用することで、Javaアプリケーションのクラス・ローディング・メカニズムをきめ細かく制御できます。 Ted Newardの著書『Server-Based Java Programming』では、Javaのクラス・ローディングのノウハウが明らかにされるとともに、J2EE APIの基礎となるJavaの概念と、J2EE APIの効果的な活用方法が示されています。
Binildas ChristudasはInfosys Communication Service Providers Practice(CSP)のSenior Technical Architectであり、Sun Microsystems Certified Enterprise ArchitectおよびMicrosoft認定プロフェッショナルとして認定されています。
