1. クラスローダーの概要

クラスローダーは、実行時にクラスをJVM (Java仮想マシン)に動的にロードする役割を果たします。これらはJRE(Javaランタイム環境)の一部でもあります。 )。 したがって、JVMは、クラスローダーのおかげでJavaプログラムを実行するために、基盤となるファイルやファイルシステムについて知る必要はありません。

さらに、これらのJavaクラスは一度にメモリにロードされるのではなく、アプリケーションで必要になったときにロードされます。 ここで、クラスローダーが登場します。 彼らはクラスをメモリにロードする責任があります。

このチュートリアルでは、さまざまなタイプの組み込みクラスローダーとそれらがどのように機能するかについて説明します。 次に、独自のカスタム実装を紹介します。

2. ビルトインクラスローダーの種類

さまざまなクラスローダーを使用してさまざまなクラスをロードする方法を学ぶことから始めましょう。

public void printClassLoaders() throws ClassNotFoundException {

    System.out.println("Classloader of this class:"
        + PrintClassLoader.class.getClassLoader());

    System.out.println("Classloader of Logging:"
        + Logging.class.getClassLoader());

    System.out.println("Classloader of ArrayList:"
        + ArrayList.class.getClassLoader());
}

上記のメソッドを実行すると、次のように出力されます。

Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62
Class loader of ArrayList:null

ご覧のとおり、ここには、アプリケーション、拡張機能、ブートストラップ( null として表示)の3つの異なるクラスローダーがあります。

アプリケーションクラスローダーは、サンプルメソッドが含まれているクラスをロードします。 アプリケーションまたはシステムクラスローダーは、クラスパスに独自のファイルをロードします。

次に、拡張クラスローダーは Logging クラスをロードします。拡張クラスローダーは、標準コアJavaクラスの拡張であるクラスをロードします。

最後に、ブートストラップクラスローダーはArrayListクラスをロードします。 ブートストラップまたは原始クラスローダーは他のすべての親です。

ただし、 ArrayList、の場合、出力にnullが表示されていることがわかります。 これは、ブートストラップクラスローダーがJavaではなくネイティブコードで記述されているため、Javaクラスとして表示されないためです。その結果、ブートストラップクラスローダーの動作は次のようになります。 JVM間で異なります。

次に、これらの各クラスローダーについて詳しく説明します。

2.1. ブートストラップクラスローダー

Javaクラスは、java.lang.ClassLoaderのインスタンスによってロードされます。 ただし、クラスローダーはそれ自体がクラスです。 したがって、問題は、 java .lang.ClassLoader 自体をロードするのは誰かということです。

ここで、ブートストラップまたは原始クラスローダーが機能します。

これは主に、JDK内部クラス(通常はrt.jarおよび$JAVA_HOME / jre / libディレクトリにあるその他のコアライブラリ)のロードを担当します。 さらに、 Bootstrapクラスローダーは、他のすべてのClassLoaderインスタンスの親として機能します。

このブートストラップクラスローダーはコアJVMの一部であり、上記の例で指摘されているように、ネイティブコードで記述されています。 プラットフォームが異なれば、この特定のクラスローダーの実装も異なる可能性があります。

2.2. 拡張クラスローダー

拡張クラスローダーはブートストラップクラスローダーの子であり、プラットフォームで実行されているすべてのアプリケーションで使用できるように、標準コアJavaクラスの拡張をロードします。

拡張クラスローダーは、JDK拡張ディレクトリ(通常は $ JAVA _HOME / lib / ext ディレクトリ、または java .ext.dirsに記載されているその他のディレクトリ)からロードされます。システムプロパティ。

2.3. システムクラスローダー

一方、システムまたはアプリケーションクラスローダーは、すべてのアプリケーションレベルのクラスをJVMにロードします。 クラスパス環境変数、-classpath、または-cpコマンドラインオプションで見つかったファイルをロードします。 また、拡張機能クラスローダーの子でもあります。

3. クラスローダーはどのように機能しますか?

クラスローダーは、Javaランタイム環境の一部です。 JVMがクラスを要求すると、クラスローダーはクラスを見つけて、完全修飾クラス名を使用してクラス定義をランタイムにロードしようとします。

java.lang.ClassLoader.loadClass()メソッドは、クラス定義をruntimeにロードする役割を果たします。 完全修飾名に基づいてクラスをロードしようとします。

クラスがまだロードされていない場合は、リクエストを親クラスローダーに委任します。 このプロセスは再帰的に発生します。

最終的に、親クラスローダーがクラスを見つけられない場合、子クラスは java .net.URLClassLoader.findClass()メソッドを呼び出して、ファイルシステム自体のクラスを探します。 

最後の子クラスローダーもクラスをロードできない場合、java.lang.NoClassDefFoundErrorまたはjava.lang.ClassNotFoundException。をスローします。

ClassNotFoundExceptionがスローされたときの出力の例を見てみましょう。

java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader    
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)    
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    
    at java.lang.Class.forName0(Native Method)    
    at java.lang.Class.forName(Class.java:348)

java .lang.Class.forName()を呼び出してすぐに一連のイベントを実行すると、最初に親クラスローダーを介してクラスをロードしようとし、次に java .net.URLClassLoader.findClass()を使用して、クラス自体を検索します。

それでもクラスが見つからない場合は、ClassNotFoundException。をスローします。

次に、クラスローダーの3つの重要な機能を調べてみましょう。

3.1. 委任モデル

クラスローダーは委任モデルに従います。クラスまたはリソースの検索要求に応じて、ClassLoaderインスタンスはクラスまたはリソースの検索を親クラスローダーに委任します。

アプリケーションクラスをJVMにロードするリクエストがあるとしましょう。 システムクラスローダーは、最初にそのクラスのロードをその親拡張クラスローダーに委任し、次に親拡張クラスローダーがそれをブートストラップクラスローダーに委任します。

ブートストラップと拡張クラスローダーがクラスのロードに失敗した場合にのみ、システムクラスローダーはクラス自体をロードしようとします。

3.2. ユニークなクラス

委任モデルの結果として、常に上向きに委任しようとするため、一意のクラスを確保するのは簡単です

親クラスローダーがクラスを見つけることができない場合にのみ、現在のインスタンスがそれ自体を見つけようとします。

3.3. 可視性

さらに、子クラスローダーは、親クラスローダーによってロードされたクラスに表示されます。

たとえば、システムクラスローダーによってロードされたクラスは、拡張クラスローダーとブートストラップクラスローダーによってロードされたクラスを可視化しますが、その逆はありません。

これを説明するために、クラスAがアプリケーションクラスローダーによってロードされ、クラスBが拡張クラスローダーによってロードされる場合、アプリケーションクラスローダーによってロードされる他のクラスに関する限り、AクラスとBクラスの両方が表示されます。

ただし、クラスBは、拡張クラスローダーによってロードされる他のクラスに表示される唯一のクラスです。

4. カスタムClassLoader

組み込みのクラスローダーは、ファイルがすでにファイルシステムにあるほとんどの場合に十分です。

ただし、ローカルハードドライブまたはネットワークからクラスをロードする必要があるシナリオでは、カスタムクラスローダーを使用する必要がある場合があります。

このセクションでは、カスタムクラスローダーのその他のユースケースをいくつか取り上げ、その作成方法を示します。

4.1. カスタムクラスローダーのユースケース

カスタムクラスローダーは、実行時にクラスをロードするだけではありません。 いくつかのユースケースには次のものが含まれます。

  1. 既存のバイトコードの変更を支援します。例: 織り剤
  2. ユーザーのニーズに動的に適したクラスを作成します。 JDBCでは、異なるドライバー実装間の切り替えは、動的なクラスのロードによって行われます。
  3. 同じ名前とパッケージのクラスに異なるバイトコードをロードしながら、クラスのバージョン管理メカニズムを実装します。 これは、URLクラスローダー(URLを介してjarをロードする)またはカスタムクラスローダーのいずれかを介して実行できます。

以下は、カスタムクラスローダーが役立つ可能性のあるより具体的な例です。

たとえば、ブラウザはカスタムクラスローダーを使用して、Webサイトから実行可能コンテンツをロードします。ブラウザは、個別のクラスローダーを使用してさまざまなWebページからアプレットをロードできます。 アプレットの実行に使用されるアプレットビューアには、ローカルファイルシステムを調べる代わりに、リモートサーバー上のWebサイトにアクセスするClassLoaderが含まれています。

次に、HTTPを介して生のバイトコードファイルをロードし、JVM内のクラスに変換します。 これらのアプレットが同じ名前であっても、異なるクラスローダーによってロードされる場合、それらは異なるコンポーネントと見なされます。

カスタムクラスローダーが関連する理由を理解したので、 ClassLoader のサブクラスを実装して、JVMがクラスをロードする方法の機能を拡張および要約しましょう。

4.2. カスタムクラスローダーの作成

説明のために、カスタムクラスローダーを使用してファイルからクラスをロードする必要があるとします。

ClassLoader クラスを拡張し、 findClass()メソッドをオーバーライドする必要があります。

public class CustomClassLoader extends ClassLoader {

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

上記の例では、デフォルトのクラスローダーを拡張し、指定されたファイルからバイト配列をロードするカスタムクラスローダーを定義しました。

5. java.lang.ClassLoaderを理解する

java .lang.ClassLoader クラスのいくつかの重要なメソッドについて説明し、それがどのように機能するかをより明確に把握しましょう。

5.1. loadClass()メソッド

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

このメソッドは、nameパラメーターを指定してクラスをロードする役割を果たします。 nameパラメーターは、完全修飾クラス名を参照します。

Java仮想マシンは、 loadClass()メソッドを呼び出してクラス参照を解決し、resolveをtrueに設定します。 ただし、クラスを解決する必要は必ずしもありません。 クラスが存在するかどうかを判断するだけでよい場合は、resolveパラメーターがfalseに設定されます。

このメソッドは、クラスローダーのエントリポイントとして機能します。

java.lang.ClassLoader:のソースコードから、 loadClass()メソッドの内部動作を理解することができます。

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException {
    
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

メソッドのデフォルトの実装は、次の順序でクラスを検索します。

  1. findLoadedClass(String)メソッドを呼び出して、クラスがすでにロードされているかどうかを確認します。
  2. 親クラスローダーでloadClass(String)メソッドを呼び出します。
  3. findClass(String)メソッドを呼び出して、クラスを検索します。

5.2. defineClass()メソッド

protected final Class<?> defineClass(
  String name, byte[] b, int off, int len) throws ClassFormatError

このメソッドは、バイトの配列をクラスのインスタンスに変換する役割を果たします。 クラスを使用する前に、それを解決する必要があります。

データに有効なクラスが含まれていない場合、ClassFormatError。がスローされます。

また、finalとしてマークされているため、このメソッドをオーバーライドすることはできません。

5.3. findClass()メソッド

protected Class<?> findClass(
  String name) throws ClassNotFoundException

このメソッドは、完全修飾名をパラメーターとして持つクラスを検索します。 クラスをロードするための委任モデルに従うカスタムクラスローダーの実装では、このメソッドをオーバーライドする必要があります。

さらに、 loadClass()は、親クラスローダーが要求されたクラスを見つけられない場合にこのメソッドを呼び出します。

クラスローダーの親がクラスを見つけられない場合、デフォルトの実装はClassNotFoundExceptionをスローします。

5.4. getParent()メソッド

public final ClassLoader getParent()

このメソッドは、委任のために親クラスローダーを返します。

セクション2で前に見たようないくつかの実装では、nullを使用してブートストラップクラスローダーを表します。

5.5. getResource()メソッド

public URL getResource(String name)

このメソッドは、指定された名前のリソースを検索しようとします。

最初に、リソースの親クラスローダーに委任します。 親がnullの場合、仮想マシンに組み込まれているクラスローダーのパスが検索されます。 

それが失敗した場合、メソッドは findResource(String)を呼び出してリソースを検索します。 入力として指定されたリソース名は、クラスパスに対して相対的または絶対的である可能性があります。

リソースを読み取るためのURLオブジェクトを返します。リソースが見つからない場合、または呼び出し元にリソースを返すための適切な権限がない場合はnullを返します。

Javaはクラスパスからリソースをロードすることに注意することが重要です。

最後に、Javaでのリソースの読み込みは、場所に依存しないと見なされます。これは、リソースを見つけるための環境が設定されている限り、コードがどこで実行されているかは関係ないためです。

6. コンテキストクラスローダー

一般に、コンテキストクラスローダーは、J2SEで導入されたクラスローディング委任スキームの代替方法を提供します。

前に学習したように、JVMのクラスローダーは階層モデルに従い、ブートストラップクラスローダーを除くすべてのクラスローダーが単一の親を持ちます。

ただし、JVMコアクラスがアプリケーション開発者によって提供されるクラスまたはリソースを動的にロードする必要がある場合、問題が発生することがあります。

たとえば、JNDIでは、コア機能は rt.jar。のブートストラップクラスによって実装されますが、これらのJNDIクラスは、独立したベンダーによって実装された(アプリケーションクラスパスにデプロイされた)JNDIプロバイダーをロードする場合があります。 このシナリオでは、ブートストラップクラスローダー(親クラスローダー)が、アプリケーションローダー(子クラスローダー)に表示されるクラスをロードする必要があります。

J2SE委任はここでは機能しません。この問題を回避するには、クラスのロードの代替方法を見つける必要があります。 これは、スレッドコンテキストローダーを使用して実現できます。

java .lang.Thread クラスには、特定のスレッドのContextClassLoaderを返すメソッド getContextClassLoader()があります。 ContextClassLoader は、リソースとクラスをロードするときにスレッドの作成者によって提供されます。

値が設定されていない場合、デフォルトで親スレッドのクラスローダーコンテキストになります。

7. 結論

クラスローダーは、Javaプログラムを実行するために不可欠です。 この記事では、それらを紹介しました。

さまざまなタイプのクラスローダー、つまりBootstrap、Extensions、およびSystemクラスローダーについて説明しました。 Bootstrapはそれらすべての親として機能し、JDK内部クラスのロードを担当します。 一方、拡張機能とシステムは、それぞれJava拡張機能ディレクトリとクラスパスからクラスをロードします。

また、クラスローダーがどのように機能するかを学び、委任、可視性、一意性などのいくつかの機能を調べました。 次に、カスタムクラスローダーを作成する方法について簡単に説明しました。 最後に、Contextクラスローダーの概要を説明しました。

いつものように、これらの例のソースコードは、GitHubにあります。