また、これらのJavaクラスは一度にメモリにロードされるわけではありませんが、アプリケーションで必要とされる場合はそうです。これがクラスローダーが登場するところです。それらはクラスをメモリにロードする責任があります。



簡単な例を使用して、さまざまなクラスローダーを使用してさまざまなクラスがどのようにロードされるかを学ぶことから始めましょう。

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:[email protected]Class loader of Logging:[email protected]Class loader of ArrayList:null

ご覧のとおり、ここには3つの異なるクラスローダーがあります。アプリケーション、拡張子、およびブートストラップ(

null

として表示)

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

次に、拡張機能oneは

Logging

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

最後に、ブートストラップは

ArrayList

クラスをロードします。ブートストラップまたは基本クラスローダは、他のすべてのものの親です。

しかし、最後の結果、

ArrayList

については出力に

null

が表示されることがわかります。

これは、ブートストラップクラスローダーがJavaではなくネイティブコードで記述されているためです – したがって、Javaクラスとしては表示されません

このため、ブートストラップクラスローダーの動作はJVMによって異なります。

それでは、これらの各クラスローダーについてさらに詳しく説明しましょう。









最後の子クラスローダーがそのクラスをロードできない場合は、


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つの重要な機能があります。









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

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

このセクションでは、カスタムクラスローダーの他のユースケースについて説明し、その作成方法を説明します。

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

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

  1. 既存のバイトコードを変更するのに役立ちます. 製織剤

  2. ユーザーのニーズに合わせて動的にクラスを作成する例えばJDBCでは、

異なるドライバ実装間の切り替えは動的クラスローディングを通じて行われます。

  1. 異なるロード中にクラスバージョン管理メカニズムを実装する

同じ名前とパッケージを持つクラスのバイトコード。これは、URLクラスローダー(URLを介したjarのロード)またはカスタムクラスローダーのいずれかを介して実行できます。

カスタムクラスローダーが役に立つかもしれない、より具体的な例があります。

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

ClassLoader

が含まれています。

そして、生のバイトコードファイルをHTTP経由でロードし、それらを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パラメータは、完全修飾クラス名を表します。

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)

    メソッドを呼び出して、クラスが

ロード済み

  1. 親クラスローダーの

    loadClass(String)

    メソッドを呼び出します.


  2. 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プログラムを実行するために不可欠です。この記事の一部として、わかりやすい紹介文を提供しました。

ブートストラップ、エクステンション、システムクラスローダーなど、クラスローダーの種類について説明しました。ブートストラップはそれらすべての親として機能し、JDK内部クラスのロードを担当します。

一方、拡張機能とシステムは、それぞれJava拡張機能のディレクトリとクラスパスからクラスをロードします。

それから、クラスローダーがどのように動作するかについて話し合い、委任、可視性、独自性などのいくつかの機能について説明し、その後カスタムの作成方法について簡単に説明しました。最後に、コンテキストクラスローダーの紹介をしました。

いつものように、コードサンプルはhttps://github.com/eugenp/tutorials/tree/master/core-java/src/main/java/com/baeldung/classloader[over on GitHub]にあります。