web-dev-qa-db-fra.com

Différence entre le chargement d'une classe à l'aide de ClassLoader et Class.forName

Ci-dessous deux extraits de code

Le premier utilise la classe ClassLoader pour charger une classe spécifiée

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

Le second utilise Class.forName () pour charger une classe spécifiée

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

Quelle est la différence entre les approches susmentionnées? Lequel sert à quelle fin?

22
Ebbu Abraham

Réponse rapide (sans échantillons de code)

Avec l'approche ClassLoader cls = <a ClassLoader>; explicite, vous avez la possibilité de charger la classe à partir d'un ClassLoader qui est pas votre ClassLoader par défaut. Dans votre cas, vous utilisez le ClassLoader système par défaut, il donne donc un résultat global similaire (avec une instanciation de la différence d'objet final) à l'appel Class.forName(String name), mais vous pouvez référencer un autre ClassLoader à la place.

Cela dit, vous pouvez également utiliser Class.forName(String name, boolean initialize, ClassLoader loader) tant que vous savez en quoi consiste ClassLoader.

Par exemple, votre application EAR possède son propre ClassLoader avec une version d'une bibliothèque d'analyse XML intégrée à celle-ci. Votre code utilise normalement ces classes, mais dans un cas, vous devez extraire une classe de désérialisation d'une version antérieure de la bibliothèque (que le serveur d'applications détient par le passé dans its global ClassLoader). Vous pouvez donc référencer ce classLoader d’Application Server à la place.

Malheureusement, jusqu'à ce que nous obtenions le projet Jigsaw (JDK 8), celui-ci est utilisé plus souvent que nous le souhaiterions :-)

14
Martijn Verburg

Les autres réponses sont très complètes en ce sens qu’elles explorent d’autres surcharges de Class.forName(...) et parlent de la possibilité d’utiliser différents ClassLoaders.

Cependant, ils ne répondent pas à votre question directe: "Quelle est la différence entre les approches susmentionnées?", Qui traite d'une surcharge spécifique de Class.forName(...). Et il leur manque une différence très importante. Initialisation de classe .

Considérez la classe suivante:

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

Considérons maintenant les deux méthodes suivantes:

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");
  }
}

La première classe, Main1, lorsqu'elle sera exécutée, produira une sortie telle que

time = 1313614183558

L'autre, cependant, ne produira aucune sortie du tout. Cela signifie que la classe A, bien que chargée, n’a pas été initialisée (c’est-à-dire que <clinit> n’a pas été appelée). En fait, vous pouvez même interroger les membres de la classe en réfléchissant avant l'initialisation!

Pourquoi voudriez-vous vous en soucier?

Il existe des classes qui effectuent une sorte d'initialisation ou d'enregistrement important lors de l'initialisation.

Par exemple, JDBC spécifie les interfaces implémentées par différents fournisseurs. Pour utiliser MySQL, vous utilisez généralement Class.forName("com.mysql.jdbc.Driver");. C'est-à-dire que vous chargez et initialisez la classe. Je n'ai jamais vu ce code, mais le constructeur statique de cette classe doit évidemment enregistrer la classe (ou quelque chose d'autre) quelque part avec JDBC.

Si vous utilisiez ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");, vous ne pourrez pas utiliser JDBC, car la classe, bien que chargée, n’a pas été initialisée (et JDBC ne saurait pas quelle implémentation utiliser, comme si vous n’aviez pas chargé la classe).

C'est donc la différence entre les deux méthodes que vous avez posées.

46
Bruno Reis

Dans votre cas concret:

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

Le code ci-dessus chargera TargetClassTOUJOURS avec system classloader .

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

Le deuxième extrait de code chargera (et initialisera) TargetClass avec le chargeur de classes utilisé pour charger la classe qui exécute cette ligne de code. Si cette classe était chargée avec System Classloader, les deux approches sont identiques (à l’exception de l’initialisation de la classe, comme expliqué dans une excellente réponse de Bruno).

Lequel utiliser? Pour le chargement et l'inspection de classes avec réflexion, il est recommandé d'utiliser un chargeur de classes spécifique (ClassLoader.loadClass()) - cela vous permet de garder le contrôle et d'éviter des problèmes potentiellement obscurs entre différents environnements.

Si vous devez charger ET initialiser, utilisez Class.forName(String, true, ClassLoader).

Comment trouver le bon chargeur de classe? Cela dépend de votre environnement:

  • si vous exécutez une application de ligne de commande , vous pouvez simplement utiliser system classloader ou le chargeur de classes qui a chargé vos classes d'application (Class.getClassLoader()).
  • si vous travaillez dans un environnement managed (JavaEE, conteneur de servlet, etc.), le mieux serait de vérifier le chargeur de classe de contexte du thread actuel d abord, puis de revenir aux options données au point précédent.
  • ou utilisez simplement votre propre chargeur de classes personnalisé (si vous aimez ce genre de chose)

En général, le plus sûr et testé serait d'utiliser ClassUtils.forName() de Spring (voir JavaDoc ).

Explication plus approfondie:


La forme la plus courante de Class.forName(), celle qui utilise un seul paramètre String, utilise toujours le chargeur de classes de l'appelant. C'est le chargeur de classes qui charge le code exécutant la méthode forName(). Par comparaison, ClassLoader.loadClass() est une méthode d'instance et vous oblige à sélectionner un chargeur de classe particulier, qui peut être ou non le chargeur qui charge ce code appelant. Si le choix d'un chargeur spécifique pour charger la classe est important pour votre conception, vous devez utiliser ClassLoader.loadClass() ou la version à trois paramètres de forName() ajoutée dans Plateforme Java 2, Standard Edition (J2SE)}: Class.forName(String, boolean, ClassLoader).

Source: Quelle est la différence entre Class.forName() et ClassLoader.loadClass()?


En outre, SPR-2611 met en évidence un cas de coin obscur intéressant lors de l'utilisation de Class.forName(String, boolean, ClassLoader).

Comme indiqué dans ce numéro de printemps, l'utilisation de ClassLoader.loadClass () est l'approche recommandée (lorsque vous devez charger des classes à partir d'un chargeur de classes spécifique).

12
Neeme Praks

Depuis le API doc :

Invoquer cette méthode équivaut à:

  Class.forName(className, true, currentLoader)

où currentLoader indique le chargeur de classe définissant la classe actuelle.

Ainsi, la principale différence réside dans le chargeur de classe qui sera utilisé (il peut être différent du chargeur de classe système). La méthode surchargée vous permettrait également de spécifier le chargeur de classe à utiliser explicitement.

1
Michael Borgwardt

ClassLoader.loadClass () charge toujours le chargeur de classes du système, alors que Class.forName () charge toutes les classes. Voyons cet exemple,

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()); 
      }
}

Lorsque vous exécutez ce programme, vous obtenez une exception à ClassLoader.getSystemClassLoader().loadClass("com.A"); 

La sortie peut être:

time = 1388864219803
Java.lang.ClassNotFoundException: com.A
1

ClassLoader.loadClass() utilise le chargeur de classes spécifié (le chargeur de classes système dans votre cas), alors que Class.forName() utilise le chargeur de classes de la classe actuelle.

Class.forName() peut être utilisé lorsque vous ne vous souciez pas d'un chargeur de classes particulier et que vous souhaitez le même comportement de chargement de classes que pour les classes référencées statiquement.

1
axtavt

La 2ème approche charge une classe en utilisant une ClassLoader 

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

Voici ce que dit JavaDoc:

forName(String name, boolean initialize, ClassLoader loader)

Le chargeur de classe spécifié est utilisé pour Charger la classe ou l'interface. Si le chargeur de paramètres Est null, la classe est chargée via le chargeur de classe d'amorçage .

Ainsi, la deuxième option utilise le ClassLoader System (qui correspond essentiellement à la première option).

0
Buhake Sindi

Mais le bloc d'initialisation statique n'est exécuté que lorsque nous utilisons class.forname ("...");

Je viens de tester.

0
Argho Chatterjee

il y a aussi une différence lors du chargement de types de tableaux. Je pense que classloader.loadClass(clazz) ne peut pas gérer les types de tableaux, mais Class.forName(clazz,true,classloader) le peut.

0
Gerhard Presser