web-dev-qa-db-ja.com

ClassLoaderとClass.forNameを使用したクラスのロードの違い

以下は2つのコードスニペットです

最初のものはClassLoaderクラスを使用して指定されたクラスをロードします

ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");

2番目はClass.forName()を使用して指定されたクラスをロードします

Class cls = Class.forName("TargetClass");

上記のアプローチの違いは何ですか。どちらがどの目的に役立ちますか?

23
Ebbu Abraham

クイックアンサー(コードサンプルなし)

明示的な_ClassLoader cls = <a ClassLoader>;_アプローチを使用すると、デフォルトのClassLoaderであるnotであるClassLoaderからクラスをロードする柔軟性が得られます。あなたの場合、デフォルトのSystem ClassLoaderを使用しているので、Class.forName(String name)呼び出しと同様の全体的な結果(最終的なオブジェクトの違いのインスタンス化を含む)が得られますが、could代わりに別のClassLoaderを参照してください。

そうは言っても、ClassLoaderが何であるかを知っている限り、Class.forName(String name, boolean initialize, ClassLoader loader)を使用することもできます。

たとえば、EARベースのアプリケーションには独自のClassLoaderがあり、その中にXML解析ライブラリのバージョンがラップされています。コードは通常これらのクラスを使用しますが、ある場合には、以前のバージョンのライブラリから逆シリアル化クラスを取得する必要があります(アプリケーションサーバーがたまたまitsクラスローダー全体に保持している)。したがって、代わりにそのアプリケーションサーバーClassLoaderを参照できます。

残念ながら、プロジェクトJigsaw(JDK 8)を入手するまで、これは私たちが望むよりも頻繁に使用されます:-)

14
Martijn Verburg

他の回答は、Class.forName(...)の他のオーバーロードを調査し、さまざまなClassLoaderを使用する可能性について話しているという点で非常に完全です。

しかし、彼らはあなたの直接の質問に答えることができません:「上記のアプローチの違いは何ですか?」、それはClass.forName(...)の1つの特定の過負荷を扱います。そして、彼らは1つの非常に重要な違いを見逃しています。 クラスの初期化

次のクラスについて考えてみます。

_public class A {
  static { System.out.println("time = " + System.currentTimeMillis()); }
}
_

ここで、次の2つの方法を検討してください。

_public class Main1 {
  public static void main(String... args) throws Throwable {
    final Class<?> c = Class.forName("A");
  }
}

public class Main2 {
  public static void main(String... args) throws Throwable {
    ClassLoader.getSystemClassLoader().loadClass("A");
  }
}
_

最初のクラス_Main1_を実行すると、次のような出力が生成されます。

_time = 1313614183558
_

ただし、もう1つは、出力をまったく生成しません。つまり、クラスAはロードされていますが、初期化されていません(つまり、_<clinit>_が呼び出されていません)。実際には、初期化の前にリフレクションを介してクラスのメンバーにクエリを実行することもできます。

なぜあなたは気にしますか?

ある種の重要な初期化または初期化時に登録を実行するクラスがあります。

たとえば、JDBCは、さまざまなプロバイダーによって実装されるインターフェースを指定します。 MySQLを使用するには、通常Class.forName("com.mysql.jdbc.Driver");を実行します。つまり、クラスをロードおよび初期化します。私はそのコードを見たことがありませんが、明らかにそのクラスの静的コンストラクターはクラス(または他の何か)をJDBCのどこかに登録する必要があります。

ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");を実行した場合、クラスはロードされていても初期化されていないため、JDBCを使用できません(JDBCは、使用していないかのように、使用する実装を認識しません。クラスをロードしました)。

だから、これはあなたが尋ねた2つの方法の違いです。

48
Bruno Reis

具体的なケースでは:

_ClassLoader cls = ClassLoader.getSystemClassLoader();
Class someClass = cls.loadClass("TargetClass");
_

上記のコードは、TargetClass[〜#〜] always [〜#〜]システムクラスローダー でロードします。

_Class cls = Class.forName("TargetClass");
_

2番目のコードスニペットは、TargetClassそのコード行を実行しているクラスをロードするために使用されたクラスローダーでロード(および初期化)します。そのクラスがシステムクラスローダーでロードされた場合、2つのアプローチは同じです(ブルーノによる優れた回答で説明されているように、クラスの初期化を除いて)。

どちらを使用しますか?リフレクションを使用してクラスをロードおよび検査するには、特定のクラスローダー(ClassLoader.loadClass())を使用することをお勧めします-あなたが管理し、異なる環境間で潜在的にあいまいな問題を回避するのに役立ちます。

ロードして初期化する必要がある場合は、Class.forName(String, true, ClassLoader)を使用します。

適切なクラスローダーを見つける方法は?環境によって異なります:

  • コマンドラインアプリケーションを実行している場合は、 システムクラスローダー またはアプリケーションクラスをロードしたクラスローダーを使用できます。 (Class.getClassLoader())。
  • 管理環境(JavaEE、サーブレットコンテナなど)内で実行している場合は、 現在のスレッドコンテキストクラスローダー)を確認するのが最善です。 最初に、次に前のポイントで指定されたオプションにフォールバックします。
  • または、独自のカスタムクラスローダーを使用します(そのようなことに興味がある場合)

一般に、最も確実なテスト済みは、SpringのClassUtils.forName()を使用することです( JavaDoc を参照)。 =)。

より詳細な説明:


Class.forName()の最も一般的な形式、つまり単一のStringパラメーターを受け取る形式は、常に呼び出し元のクラスローダーを使用します。これは、forName()メソッドを実行するコードをロードするクラスローダーです。比較すると、ClassLoader.loadClass()はインスタンスメソッドであり、特定のクラスローダーを選択する必要があります。クラスローダーは、その呼び出しコードをロードするローダーである場合とそうでない場合があります。クラスをロードするために特定のローダーを選択することが設計にとって重要である場合は、ClassLoader.loadClass()またはJava2に追加されたforName()の3パラメーターバージョンを使用する必要があります。プラットフォーム、Standard Edition(J2SE)Class.forName(String, boolean, ClassLoader)

出典: Class.forName()ClassLoader.loadClass()の違いは何ですか?


また、 SPR-2611 は、Class.forName(String, boolean, ClassLoader)を使用するときに、1つの興味深いあいまいなコーナーケースを強調しています。

その春の問題に見られるように、ClassLoader.loadClass()の使用は推奨されるアプローチです(特定のクラスローダーからクラスをロードする必要がある場合)。

12
Neeme Praks

APIドキュメント から:

このメソッドを呼び出すことは、次と同等です。

  Class.forName(className, true, currentLoader)

ここで、currentLoaderは、現在のクラスの定義クラスローダーを示します。

したがって、主な違いは、どのクラスローダーが使用されるかです(システムクラスローダーと同じ場合と同じでない場合があります)。オーバーロードされたメソッドでは、明示的に使用するクラスローダーを指定することもできます。

1

ClassLoader.loadClass()は常にシステムクラスローダーをロードしますが、Class.forName()は任意のクラスをロードします。この例を見てみましょう、

_package com;
public class TimeA {
      public static void main (String args[]) {
            try {
                final Class c = Class.forName("com.A");
                ClassLoader.getSystemClassLoader().loadClass("com.A");
            }catch(ClassNotFoundException ex) {
                System.out.println(ex.toString());
            }
      }
}

class A {
      static {
          System.out.println("time = " + System.currentTimeMillis()); 
      }
}
_

このプログラムを実行すると、ClassLoader.getSystemClassLoader().loadClass("com.A");で例外が発生します。

出力は次のようになります。

_time = 1388864219803
Java.lang.ClassNotFoundException: com.A
_
1

ClassLoader.loadClass()は指定されたクラスローダー(この場合はシステムクラスローダー)を使用しますが、Class.forName()は現在のクラスのクラスローダーを使用します。

Class.forName()は、特定のクラスローダーを気にせず、静的に参照されるクラスと同じクラスローディング動作が必要な場合に使用できます。

1
axtavt

2番目のアプローチは、ClassLoaderを使用してクラスをロードします

 public static Class<?> forName(String className) 
                throws ClassNotFoundException {
        return forName0(className, true, ClassLoader.getCallerClassLoader());

これはJavaDocが言うことです:

forName(String name, boolean initialize, ClassLoader loader)

指定されたクラスローダーは、クラスまたはインターフェイスをロードするために使用されます。パラメータローダーがnullの場合、クラスはbootstrapクラスローダーを介してロードされます。

したがって、2番目のオプションはSystem ClassLoaderを使用します(つまり、最初のオプションで行うことです)。

0
Buhake Sindi

ただし、静的初期化ブロックは、class.forname( "...");を使用する場合にのみ実行されます。

テストしたばかりです。

0

配列型をロードするときも違いがあります。 classloader.loadClass(clazz)は配列型を処理できないと思いますが、Class.forName(clazz,true,classloader)は処理できます。

0
Gerhard Presser