ホームページ  >  記事  >  Java  >  Java クラスローダーの詳細

Java クラスローダーの詳細

Y2J
Y2Jオリジナル
2017-04-17 14:27:101281ブラウズ

クラスローダーは、Java™ において非常に重要な概念です。クラス ローダーは、Java クラスのバイト コードを Java 仮想マシンにロードする役割を果たします。この記事では、まず、プロキシ モード、クラスやスレッド コンテキスト クラス ローダーをロードする具体的なプロセスなど、Java クラス ローダーの 基本概念を詳細に紹介し、次に独自のクラス ローダーを開発する方法を紹介し、最後にWeb コンテナおよび OSGi™ のアプリケーション。

IBM Bluemix クラウド プラットフォームで次のアプリを開発してデプロイします。
トライアルを開始してください
クラス ローダーは Java 言語の革新であり、Java 言語の人気の重要な理由の 1 つです。これにより、Java クラスを Java 仮想マシンに動的にロードして実行できるようになります。クラス ローダーは JDK 1.0 から存在しており、元々は Java アプレットのニーズを満たすために開発されました。 Java アプレットは、Java クラス ファイルをリモートからブラウザにダウンロードして実行する必要があります。クラスローダーは現在、Web コンテナーや OSGi で広く使用されています。一般に、Java アプリケーションの開発者はクラス ローダーと直接対話する必要はありません。 Java 仮想マシンのデフォルトの 動作 は、ほとんどの状況のニーズを満たすのに十分です。ただし、クラス ローダーと対話する必要がある状況に遭遇し、クラス ローダーのメカニズムについてあまり知らない場合は、ClassNotFoundException や NoClassDefFoundError などのデバッグに多くの時間を費やすことが簡単にあります。例外。この記事では、読者が Java 言語におけるこの重要な概念を深く理解できるように、Java のクラス ローダーを詳しく紹介します。以下では、まず関連する基本概念をいくつか紹介します。 クラスローダーの基本概念
その名前が示すように、クラスローダーは、JavaクラスをJava仮想マシンにロードするために使用されます。一般に、Java 仮想マシンは次のように Java クラスを使用します。 Java ソース プログラム (.java ファイル) は、Java コンパイラによってコンパイルされた後、Java バイト コード (.class ファイル) に変換されます。クラス ローダーは、Java バイト コードを読み取り、それを java.lang.Class クラスのインスタンスに変換する役割を果たします。このような各インスタンスは Java クラスを表します。このクラスの オブジェクト
は、このインスタンスの newInstance() メソッドを通じて作成できます。たとえば、Java バイト コードはツールを通じて動的に生成されたり、ネットワークを通じてダウンロードされたりする場合があります。 基本的にすべてのクラスローダーは java.lang.ClassLoader クラスのインスタンスです。この Java クラスについては、以下で詳しく説明します。
java.lang.ClassLoader**
クラスの紹介** java.lang.ClassLoader クラスの基本的な役割は、指定されたクラスの名前に基づいて、これらのバイト コードから対応するバイト コードを検索または生成することです。 java.lang.Class クラスのインスタンスである Java クラスを定義します。さらに、ClassLoader は、画像ファイルや設定ファイル
など、Java アプリケーションに必要なリソースをロードする役割も担います。ただし、この記事ではクラスをロードする機能についてのみ説明します。クラスのロードを完了するために、ClassLoader は一連のメソッドを提供します。より重要なメソッドを表 1 に示します。これらの方法の詳細については、以下で説明します。

表 1. ClassLoader でのクラスのロードに関連するメソッド

メソッドの説明

getParent()

このクラスローダーの親クラスローダーを返します。

loadClass(

String

name) name という名前のクラスをロードし、返される結果は java.lang.Class クラスのインスタンスです。

findClass(String name)

name という名前のクラスを検索すると、返される結果は java.lang.Class クラスのインスタンスです。

findLoadedClass(String name)

name という名前のロードされたクラスを検索します。返される結果は java.lang.Class クラスのインスタンスです。

defineClass(String name, byte[] b, int off, int len)

バイト

array

b の内容を Java クラスに変換し、返される結果は java.lang.Class クラスのインスタンスです。 。このメソッドは final として宣言されています。 resolveClass(Class6b3d0130bba23ae47fe2b8e8cddf0195 c)

指定された Java クラスをリンクします。

表 1 に示すメソッドの場合、クラス名を表す name パラメーターの値は、クラスのバイナリ名です。注意が必要なのは、com.example.Sample$1 や com.example.Sample$Inner などの内部クラスの表現です。これらのメソッドについては、クラス ローダーの動作メカニズムを紹介するときにさらに詳しく説明します。以下に、クラスローダのツリー状の組織構造について説明します。

クラスローダーのツリー状の組織構造

Java のクラスローダーは、システムによって提供されるものと、Java アプリケーション開発者によって作成されるものの 2 つのカテゴリに大別できます。システムによって提供される主なクラス ローダーは 3 つあります:
ブートストラップ クラス ローダー (ブートストラップ クラス ローダー): Java のコア ライブラリをロードするために使用され、ネイティブ コードで実装され、java から 継承しません。 .ClassLoader。 拡張クラスローダー: Java 拡張ライブラリをロードするために使用されます。 Java 仮想マシンの実装では、拡張ライブラリのディレクトリが提供されます。クラス ローダーは、このディレクトリで Java クラスを検索してロードします。
システム クラス ローダー: Java アプリケーションのクラス パス (CLASSPATH) に従って Java クラスをロードします。一般に、Java アプリケーション クラスはこれによってロードされます。これは、ClassLoader.getSystemClassLoader() を通じて取得できます。

システムによって提供されるクラスローダーに加えて、開発者は、特別なニーズを満たすために java.lang.ClassLoader クラスを継承することによって独自のクラスローダーを実装できます。

ブートストラップ クラス ローダーを除き、すべてのクラス ローダーには親クラス ローダーがあります。これは、表 1 に示す getParent() メソッドを通じて取得できます。システムによって提供されるクラスローダーの場合、システムクラスローダーの親クラスローダーは拡張クラスローダーであり、開発者によって作成されたクラスローダーの場合、拡張クラスローダーの親クラスローダーはブートクラスローダーです。クラスローダーは、このクラスローダーの Java クラスをロードするクラスローダーです。なぜなら、クラスローダー Java クラスは、
他の Java クラスと同様に、クラスローダーによってロードされるからです。一般に、開発者が作成したクラスローダーの親クラスローダーはシステムクラスローダーです。クラスローダーはこのように編成され、ツリー構造を形成します。ツリーのルート ノードはブート クラス ローダーです。図 1 は、典型的なクラス ローダー ツリーの組織構造図を示しています。矢印は親クラス ローダーを指しています。
図 1. クラス ローダーのツリー構成構造の概略図

Java クラスローダーの詳細

classload_tree.png

コード リスト 1 は、クラス ローダーのツリー構成構造を示しています。

リスト 1. デモのクラスローダーツリー構成構造

public class ClassLoaderTree {
    public static void main(String[] args) { 
    ClassLoader loader = ClassLoaderTree.class.getClassLoader(); 
    while (loader != null) { 
        System.out.println(loader.toString()); 
        loader = loader.getParent(); 
    } 
    }
}

各 Java クラスは、それを定義するクラスローダーを指す

reference を維持します。この参照は getClassLoader() メソッドを通じて取得できます。コード リスト 1 では、getParent() メソッドが再帰的に呼び出され、すべての親クラス ローダーが出力されます。コード リスト 1 の実行結果をコード リスト 2 に示します。 リスト 2. クラス ローダーのツリー状の組織構造の実行結果を示します
sun.misc.Launcher$AppClassLoader@9304b1 sun.misc.Launcher$ExtClassLoader@190d11 コード リスト 2 に示すように、最初の出力は ClassLoaderTree クラスのクラス ローダー、つまりシステム クラス ローダーです。これは sun.misc.Launcher$AppClassLoader クラスのインスタンスであり、2 番目の出力は拡張クラス ローダーであり、sun.misc.Launcher$ExtClassLoader クラスのインスタンスです。ここではブート クラス ローダーが出力されないことに注意してください。これは、一部の JDK 実装が、親クラス ローダーがブート クラス ローダーである場合に null を返すためです。
クラスローダーのツリー状の組織構造を理解した後、クラスローダーのプロキシモードを紹介しましょう。

クラスローダーのプロキシモード

クラスローダーが特定のクラスのバイトコードを見つけて定義しようとすると、まずその親クラスローダーにプロキシされ、親クラスローダーは最初にこのカテゴリをロードしようとします。等々。プロキシ パターンの背後にある動機を紹介する前に、まず Java 仮想マシンが 2 つの Java クラスが同じであるとどのように判断するかを説明する必要があります。 Java 仮想マシンは、クラスの完全名が同じかどうかだけでなく、このクラスをロードするクラス ローダーが同じかどうかも調べます。 2 つのクラスは、両方が同じである場合にのみ同じとみなされます。同じバイトコードを異なるクラスローダでロードしても、得られるクラスは異なります。たとえば、Java クラス com.example.Sample は、コンパイル後にバイト コード ファイル Sample.class を生成します。 2 つの異なるクラス ローダー、ClassLoaderA と ClassLoaderB はそれぞれ、この Sample.class ファイルを読み取り、このクラスを表す java.lang.Class クラスの 2 つのインスタンスを定義します。これら 2 つのインスタンスは同一ではありません。 Java 仮想マシンにとって、これらは異なるクラスです。これら 2 つのクラスのオブジェクトを相互に割り当てようとすると、実行時例外 ClassCastException がスローされます。以下に例を挙げて詳しく説明します。 Java クラス com.example.Sample をコード リスト 3 に示します。 リスト 3.com.example.Sample クラス

package com.example;
public class Sample {    
    private Sample instance;
    public void setSample(Object instance) { 
    this.instance = (Sample) instance; 
    }
}

如 代码清单 3所示,com.example.Sample类的方法 setSample接受一个 java.lang.Object类型的参数,并且会把该参数强制转换成com.example.Sample类型。测试 Java 类是否相同的代码如 代码清单 4所示。
清单 4. 测试 Java 类是否相同

public void testClassIdentity() {
    String classDataRootPath = "C:\workspace\Classloader\classData";
    
File
SystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
    FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
    String className = "com.example.Sample";
    try {
        Class<?> class1 = fscl1.loadClass(className);
        Object obj1 = class1.newInstance();
        Class<?> class2 = fscl2.loadClass(className);
        Object obj2 = class2.newInstance();
        Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
        setSampleMethod.invoke(obj1, obj2);
    } catch (Exception e) {
        e.printStackTrace();
    }
 }

代码清单 4中使用了类 FileSystemClassLoader的两个不同实例来分别加载类 com.example.Sample,得到了两个不同的java.lang.Class的实例,接着通过 newInstance()方法分别生成了两个类的对象 obj1和 obj2,最后通过 Java 的反射 API 在对象 obj1上调用方法 setSample,试图把对象 obj2赋值给 obj1内部的 instance对象。代码清单 4的运行结果如 代码清单 5所示。
清单 5. 测试 Java 类是否相同的运行结果
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)
at classloader.ClassIdentity.main(ClassIdentity.java:9)
Caused by: java.lang.ClassCastException: com.example.Sample
cannot be cast to com.example.Sample
at com.example.Sample.setSample(Sample.java:7)
... 6 more
从 代码清单 5给出的运行结果可以看到,运行时抛出了 java.lang.ClassCastException异常。虽然两个对象 obj1和 obj2的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被 Java 虚拟机认为是相同的。
了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细介绍。
下面具体介绍类加载器加载类的详细过程。
加载类的过程
在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是java.lang.NoClassDefFoundError异常。
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。
下面讨论另外一种类加载器:线程上下文类加载器。
线程上下文类加载器
スレッド コンテキスト クラス ローダー (コンテキスト クラス ローダー) は、JDK 1.2 から導入されました。クラス java.lang.Thread のメソッド getContextClassLoader() および setContextClassLoader(ClassLoader cl) は、スレッドのコンテキスト クラス ローダーを取得および設定するために使用されます。 setContextClassLoader(ClassLoader cl) メソッドを通じて設定されていない場合、スレッドは親スレッドのコンテキスト クラス ローダーを継承します。 Java アプリケーションが実行される初期スレッドのコンテキスト クラス ローダーは、システム クラス ローダーです。スレッド内で実行されているコードは、このタイプのローダーを通じてクラスとリソースをロードできます。
上記のクラスローダーのプロキシモードは、Javaアプリケーション開発で遭遇するクラスローダーの問題をすべて解決できるわけではありません。 Java は多くのサービス プロバイダー インターフェイス (サービス プロバイダー インターフェイス、SPI) を提供し、サードパーティがこれらのインターフェイスの実装を提供できるようにします。一般的な SPI には、JDBC、JCE、JNDI、JAXP、JBI などがあります。これらの SPI インターフェイスは、Java コア ライブラリによって提供されます。たとえば、JAXP の SPI インターフェイス定義は javax.xml.parsers パッケージに含まれています。これらの SPI 実装コードは、Java アプリケーションが依存する jar パッケージとして含まれる可能性があり、JAXP SPI を実装する Apache Xerces に含まれる jar パッケージなど、クラス パス (CLASSPATH) を通じて見つけることができます。 SPI インターフェイスのコードは、多くの場合、特定の実装クラスをロードする必要があります。たとえば、JAXP の javax.xml.parsers.DocumentBuilderFactory クラスの newInstance() メソッドは、DocumentBuilderFactory の新しいインスタンスを生成するために使用されます。ここでのインスタンスの実際のクラスは、SPI 実装によって提供される javax.xml.parsers.DocumentBuilderFactory から継承されます。たとえば、Apache Xerces では、実装されるクラスは org.apache.xerces.jaxp.DocumentBuilderFactoryImpl です。問題は、SPI インターフェイスが Java コア ライブラリの一部であり、ブート クラス ローダーによってロードされることです。SPI によって実装された Java クラスは、通常、システム クラス ローダーによってロードされます。ブート クラス ローダーは Java コア ライブラリのみをロードするため、SPI 実装クラスを見つけることができません。また、これはシステム クラス ローダーの祖先クラス ローダーであるため、システム クラス ローダーにプロキシすることもできません。つまり、クラスローダーのプロキシモードではこの問題を解決できません。 スレッドコンテキストクラスローダーはこの問題を解決します。設定が行われていない場合、Java アプリケーション スレッドのコンテキスト クラス ローダーは、デフォルトのシステム コンテキスト クラス ローダーになります。 SPI インターフェイス コードでスレッド コンテキスト クラス ローダーを使用すると、SPI 実装クラスを正常にロードできます。スレッド コンテキスト クラス ローダーは、多くの SPI 実装で使用されます。 以下では、クラスをロードする別の方法、Class.forName を紹介します。

Class.forName
Class.forName は、クラスのロードにも使用できるstatic
メソッドです。このメソッドには、Class.forName(String name, boolean initialize, ClassLoader ローダー) と Class.forName(String className) の 2 つの形式があります。パラメータ名の最初の形式はクラスの完全名を表し、initialize はクラスを初期化するかどうかを表し、loader はロード時に使用されるクラス ローダーを表します。 2 番目の形式は、パラメーターの値 initialize を true に設定し、loader の値を現在のクラスのクラス ローダーに設定することと同じです。 Class.forName の非常に一般的な使用法は、データベース ドライバー をロードするときです。たとえば、Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() は、Apache Derby データベースのドライバーをロードするために使用されます。 クラスローダーに関連する基本概念を紹介した後、独自のクラスローダーを開発する方法を説明します。

独自のクラスローダーを開発します

ただし、ほとんどの場合、システムによってデフォルトで提供されるクラスローダー実装でニーズを満たすことができます。ただし、場合によっては、アプリケーション用に独自のクラス ローダーを開発する必要があります。たとえば、アプリケーションは、セキュリティを確保するために、ネットワーク経由で Java クラスのバイト コードを送信します。現時点では、特定のネットワーク アドレスから暗号化されたバイト コードを読み取り、復号化して検証し、最終的に Java 仮想マシンで実行するクラスを定義する独自のクラス ローダーが必要です。以下では、2 つの具体的な例を通してクラスローダーの開発を説明します。

ファイル システムクラス ローダー最初のクラス ローダーは、ファイル システムに保存されている Java バイト コードをロードするために使用されます。完全な実装をコード リスト 6 に示します。

リスト 6. ファイル システム クラス ローダー

public class FileSystemClassLoader extends ClassLoader {
    private String rootDir; 
    public FileSystemClassLoader(String rootDir) { 
    this.rootDir = rootDir; 
} 
    protected Class<?> findClass(String name) throws ClassNotFoundException { 
    byte[] classData = getClassData(name); 
    if (classData == null) { 
        throw new ClassNotFoundException(); 
    } 
    else { 
        return defineClass(name, classData, 0, classData.length); 
    } 
} 
    private byte[] getClassData(String className) { 
    String path = classNameToPath(className); 
    try { 
        InputStream ins = new FileInputStream(path); 
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
        int bufferSize = 4096; 
        byte[] buffer = new byte[bufferSize]; 
        int bytesNumRead = 0; 
        while ((bytesNumRead = ins.read(buffer)) != -1) { 
            baos.write(buffer, 0, bytesNumRead); 
        } 
        return baos.toByteArray(); 
    } catch (IOException e) { 
        e.printStackTrace(); 
    } 
    return null; 
} 
    private String classNameToPath(String className) { 
    return rootDir + File.separatorChar 
            + className.replace(&#39;.&#39;, File.separatorChar) + ".class"; 
    }
}

如 代码清单 6所示,类 FileSystemClassLoader继承自类 java.lang.ClassLoader。在 表 1中列出的 java.lang.ClassLoader类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现。该方法会首先调用 findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用 findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。
类 FileSystemClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。

网络类加载器
下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。
类 NetworkClassLoader负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader类似。在通过NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用 Java 反射 API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 下载。
在介绍完如何开发自己的类加载器之后,下面说明类加载器和 Web 容器的关系。

类加载器与 Web 容器
对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:
每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。
多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

在介绍完类加载器与 Web 容器的关系之后,下面介绍它与 OSGi 的关系。

クラスローダーとOSGi
OSGi™はJava上の動的モジュールシステムです。これは開発者にサービス指向のコンポーネントベースのランタイム環境を提供し、ソフトウェアの ライフ サイクル を管理する標準的な方法を提供します。 OSGi は多くの製品に実装および展開されており、オープンソース コミュニティで幅広いサポートを受けています。 Eclipse は OSGi テクノロジーに基づいて構築されています。
OSGi のすべてのモジュール (バンドル) には Java パッケージとクラスが含まれています。モジュールは、依存する他のモジュールの Java パッケージおよびクラスを宣言でき (Import-Package 経由)、また、他のモジュールで使用するために独自のパッケージおよびクラスを宣言およびエクスポート (Export-Package 経由) することもできます。 。これは、モジュール内の特定の Java パッケージとクラスを非表示にして共有できる必要があることを意味します。これは、OSGi 独自のクラス ローダー メカニズムによって実現されます。 OSGi の各モジュールには、対応するクラス ローダーがあります。これは、モジュール自体に含まれる Java パッケージとクラスをロードする役割を果たします。 Java コア ライブラリ (Java で始まるパッケージおよびクラス) からクラスをロードする必要がある場合、親クラス ローダー (通常は起動クラス ローダー) にプロキシしてロードを完了します。インポートされた Java クラスをロードする必要がある場合、Java クラスをエクスポートするモジュールにロードを完了するよう委譲します。モジュールは、親クラス ローダーによってロードされる必要がある特定の Java パッケージおよびクラスを明示的に宣言することもできます。システムプロパティ org.osgi.framework.bootdelegation の値を設定するだけです。
BundleA と BundleB という 2 つのモジュールがあり、どちらにも独自の対応するクラスローダー classLoaderA と classLoaderB があるとします。 BundleA には com.bundleA.Sample クラスが含まれており、このクラスはエクスポート済みとして宣言されています。これは、他のモジュールで使用できることを意味します。 BundleB は、bundleA によって提供されるクラス com.bundleA.Sample をインポートし、com.bundleA.Sample を継承するクラス com.bundleB.NewSample を含むことを宣言します。 BundleB が開始されると、そのクラス ローダー classLoaderB はクラス com.bundleB.NewSample をロードする必要があり、さらにクラス com.bundleA.Sample をロードする必要があります。 BundleB はクラス com.bundleA.Sample をインポートすることを宣言しているため、classLoaderB はクラス com.bundleA.Sample をロードする作業を、このクラスをエクスポートする BundleA のクラスローダー classLoaderA に委任します。 classLoaderA は、そのモジュール内でクラス com.bundleA.Sample を検索し、それを定義します。結果のクラス com.bundleA.Sample インスタンスは、このクラスを宣言するすべてのモジュールで使用できます。 java で始まるクラスの場合、それらは親クラス ローダーによってロードされます。システム プロパティ org.osgi.framework.bootdelegation=com.example.core.* が宣言されている場合、親クラス ローダーはパッケージ com.example.core 内のクラスのロードを完了します。
OSGi モジュールのクラス ローダー構造により、クラスの異なるバージョンが Java 仮想マシン内で共存できるようになり、優れた柔軟性がもたらされます。ただし、この違いは、特にモジュールがサードパーティが提供するライブラリを使用する必要がある場合に、開発者にいくつかの問題をもたらします。ここにいくつかの良い提案があります:
クラス ライブラリが 1 つのモジュールのみで使用されている場合は、クラス ライブラリの jar パッケージをモジュールに配置し、Bundle-ClassPath で指定します。 クラス ライブラリが複数のモジュールで共有されている場合は、このクラス ライブラリ用に別のモジュールを作成し、他のモジュールがエクスポートとして使用する必要がある Java パッケージを宣言できます。他のモジュール宣言はこれらのクラスをインポートします。
クラス ライブラリが SPI インターフェイスを提供し、スレッド コンテキスト クラス ローダーを使用して SPI によって実装された Java クラスをロードする場合、Java クラスが見つからない可能性があります。 NoClassDefFoundError 例外が発生した場合は、まず現在のスレッドのコンテキスト クラス ローダーが正しいかどうかを確認します。クラスローダーは、Thread.
currentThread().getContextClassLoader()を通じて取得できます。このクラス ローダーは、このモジュールに対応するクラス ローダーである必要があります。そうでない場合は、まず class.getClassLoader() を通じてモジュールに対応するクラス ローダーを取得し、次に Thread.currentThread().setContextClassLoader() を通じて現在のスレッドのコンテキスト クラス ローダーを設定します。

概要クラスローダーは、Java 言語の革新です。これにより、ソフトウェア コンポーネントを動的に
インストールし、更新することが可能になります。この記事では、基本概念、プロキシ モード、スレッド コンテキスト クラス ローダー、Web コンテナや OSGi との関係など、クラス ローダーに関連するトピックを詳しく紹介します。開発者が ClassNotFoundException や NoClassDefFoundError などの例外に遭遇した場合、例外をスローしたクラスのクラス ローダーと現在のスレッドのコンテキスト クラス ローダーをチェックして問題を発見する必要があります。独自のクラスローダーを開発する場合は、既存のクラスローダーの組織構造との調整に注意する必要があります。

以上がJava クラスローダーの詳細の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。