Javaでクラスをロードします。 練習する

この記事は、 Javaでクラスをロードする記事の続きです 理論



この記事では、プラグインモジュラーアーキテクチャを使用したアプリケーションフレームワークの実装について説明します。 アプリケーションエンジンとして、カスタムクラスローダーが使用され、追加のアプリケーションプラグインがロードされます。



アプリケーションコードはオリジナルのふりをするのではなく、カスタムクラスローダーと動的コードの呼び出し方法を記述する方法と原則のみを説明します。



やる気





多くの場合、複雑なシステムのアーキテクチャには、動的なコード読み込みメカニズムの使用が含まれます。 これは、実行時にどのコードが実行されるかが事前にわからない場合に必要です。 たとえば、プログラマー向けの有名なゲームRobocodeは、独自のクラスローダーを使用してカスタムタンクをゲームにロードします。 特定のインターフェイスを介してアプリケーションから分離して開発されたモジュールとして、独立したタンクを検討できます。 この記事では、最も単純化されたレベルでのみ、同様の状況が考慮されます。



さらに、コードを動的にロードするメカニズムを使用するより明らかな例がいくつかあります。 クラスのバイトコードがデータベースに保存されているとしましょう。 明らかに、このようなクラスをロードするには、特別なローダーが必要です。そのローダーには、データベースからのクラスコードの選択も含まれます。



クラスは、ネットワーク/インターネット経由でダウンロードする必要がある場合があります。 そのためには、ネットワークプロトコルのいずれかを使用してバイトコードを受信できるブートローダーが必要です。 また、JavaクラスライブラリURLClassLoaderにある既存のものを強調表示することもできます。これにより、URLの指定されたパスにあるクラスをロードできます。



準備する





記事のフレームワーク内に実装されたアプリケーションは、コードをJREに動的にロードして実行するためのエンジンのフレームワークになります。 各モジュールは、Moduleインターフェースを実装する単一のJavaクラスになります。 呼び出しには、すべてのモジュールに共通のインターフェースが必要です。 ここでは、動的コードを実行する別の方法があります-Java Reflection APIがあることを理解することが重要です。 ただし、より明確かつ単純にするために、共通のインターフェースを持つモデルが使用されます。



カスタムローダーを実装する場合、次の点に注意することが重要です。

1)ローダーは、明示的または暗黙的にjava.lang.ClassLoaderクラスを拡張する必要があります。

2)ブートローダーは、ロードの委任モデルをサポートし、階層を形成する必要があります。

3)java.lang.ClassLoaderクラスは、直接読み込みメソッド-defineClass(...)を既に実装しています。defineClass(...)は、バイトコードをjava.lang.Classに変換して検証します。

4)再帰検索メカニズムはjava.lang.ClassLoaderクラスにも実装されており、それを処理する必要はありません。

5)ローダーを正しく実装するには、java.lang.ClassLoaderクラスのfindClass()メソッドをオーバーライドするだけで十分です。



上記のリストの最後の項目を説明するためにloadClass()メソッドを呼び出すときのクラスローダーの動作を詳細に検討してみましょう。



デフォルトの実装は、次の一連のアクションを意味します。

1)キャッシュ内のロード可能なクラスを検索するためのfindLoadedClass()の呼び出し。

2)キャッシュにクラスがない場合、getParent()。LoadClass()が呼び出され、ブート権限を親ローダーに委任します。

3)親ローダー階層がクラスをロードできなかった場合、クラスを直接ロードするためにfindClass()が呼び出されます。



したがって、ローダーを正しく実装するには、指定されたシナリオ-findClass()メソッドをオーバーライドすることをお勧めします。



実装





モジュールのインターフェースを定義します。 モジュールにまずロード(ロード)させ、次に実行(実行)させて結果を返し、それからアンロード(アンロード)させます。 このコードは、モジュールを開発するためのAPIです。 個別にコンパイルし、メインアプリケーションとは別に配信するために* .jarにパッケージ化できます。



public interface Module {



public static final int EXIT_SUCCESS = 0;

public static final int EXIT_FAILURE = 1;



public void load();

public int run();

public void unload();



}



* This source code was highlighted with Source Code Highlighter .








モジュールローダーの実装を検討してください。 このローダーは、pathtobin変数で指定されたパスの特定のディレクトリからクラスコードをロードします。

import java.io. File ;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;



public class ModuleLoader extends ClassLoader {



/**

* .

*/

private String pathtobin;



public ModuleLoader( String pathtobin, ClassLoader parent) {

super(parent);

this .pathtobin = pathtobin;

}



@Override

public Class<?> findClass( String className) throws ClassNotFoundException {

try {

/**

* -

*/

byte b[] = fetchClassFromFS(pathtobin + className + ".class" );

return defineClass(className, b, 0, b.length);

} catch (FileNotFoundException ex) {

return super.findClass(className);

} catch (IOException ex) {

return super.findClass(className);

}



}



/**

* www.java-tips.org/java-se-tips/java.io/reading-a-file-into-a-byte-array.html

*/

private byte [] fetchClassFromFS( String path) throws FileNotFoundException, IOException {

InputStream is = new FileInputStream( new File (path));



// Get the size of the file

long length = new File (path).length();



if (length > Integer.MAX_VALUE) {

// File is too large

}



// Create the byte array to hold the data

byte [] bytes = new byte [( int )length];



// Read in the bytes

int offset = 0;

int numRead = 0;

while (offset < bytes.length

&& (numRead= is .read(bytes, offset, bytes.length-offset)) >= 0) {

offset += numRead;

}



// Ensure all the bytes have been read in

if (offset < bytes.length) {

throw new IOException( "Could not completely read file " +path);

}



// Close the input stream and return bytes

is .close();

return bytes;



}

}



* This source code was highlighted with Source Code Highlighter .








次に、モジュール読み込みエンジンの実装を検討します。 モジュール(.classファイル)のあるディレクトリは、アプリケーションのパラメーターとして指定されます。



import java.io. File ;



public class ModuleEngine {



public static void main( String args[]) {

String modulePath = args[0];

/**

* .

*/

ModuleLoader loader = new ModuleLoader(modulePath, ClassLoader.getSystemClassLoader());



/**

* .

*/

File dir = new File (modulePath);

String [] modules = dir.list();



/**

* .

*/

for ( String module: modules) {

try {

String moduleName = module.split( ".class" )[0];

Class clazz = loader.loadClass(moduleName);

Module execute = (Module) clazz.newInstance();



execute.load();

execute.run();

execute.unload();



} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}





}



}



* This source code was highlighted with Source Code Highlighter .








最も単純なモジュールを実装します。このモジュールは、実行の段階に関する情報を標準出力に単に出力します。 これは、クラスモジュール(API)を持つコンパイル済み.jarファイルへのパスをCLASSPATHに追加することにより、別のアプリケーションで実行できます。



public class ModulePrinter implements Module {



@Override

public void load() {

System. out .println( "Module " + this .getClass() + " loading ..." );

}



@Override

public int run() {

System. out .println( "Module " + this .getClass() + " running ..." );

return Module.EXIT_SUCCESS;

}



@Override

public void unload() {

System. out .println( "Module " + this .getClass() + " inloading ..." );

}

}



* This source code was highlighted with Source Code Highlighter .








このコードをコンパイルすると、単一のクラスファイル形式の結果を別のディレクトリにコピーできます。このディレクトリへのパスは、メインアプリケーションのパラメータとして指定する必要があります。



少し皮肉な



動的なコードの読み込み、システムをユーザーに拡張する責任を裏切る素晴らしい合法的な方法;)



All Articles