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 .


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

少し皮肉な


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

Source: https://habr.com/ru/post/J104229/


All Articles