Java, Classpath, Classloading => Plusieurs versions du même jar / projet

119

Je sais que cela peut être une question idiote pour les codeurs expérimentés. Mais j'ai une bibliothèque (un client http) dont certains des autres frameworks / jars utilisés dans mon projet ont besoin. Mais tous nécessitent différentes versions majeures telles que:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

Le chargeur de classe est-il assez intelligent pour les séparer d'une manière ou d'une autre? Préférablement pas? Comment le Classloader gère-t-il cela, au cas où une classe serait la même dans les trois fichiers JAR. Lequel est chargé et pourquoi?

Le Classloader ne récupère-t-il qu'un seul bocal ou mélange-t-il les classes de manière arbitraire? Par exemple, si une classe est chargée depuis la version 1.jar, toutes les autres classes chargées depuis le même chargeur de classe iront toutes dans le même fichier jar?

Comment gérez-vous ce problème?

Y a-t-il une astuce pour "incorporer" les fichiers jar dans le fichier "required.jar" afin que les soient considérés comme "une unité / paquet" par le Classloader, ou liés d'une manière ou d'une autre?

jens
la source

Réponses:

58

Les problèmes liés au chargeur de classe sont une question assez complexe. Vous devez dans tous les cas garder à l'esprit certains faits:

  • Les chargeurs de classe dans une application sont généralement plus d'un seul. Le chargeur de classe d'amorçage délègue au fichier approprié. Lorsque vous instanciez une nouvelle classe, le chargeur de classe plus spécifique est appelé. S'il ne trouve pas de référence à la classe que vous essayez de charger, il la délègue à son parent, et ainsi de suite, jusqu'à ce que vous arriviez au chargeur de classe d'amorçage. Si aucun d'entre eux ne trouve de référence à la classe que vous essayez de charger, vous obtenez une ClassNotFoundException.

  • Si vous avez deux classes avec le même nom binaire, recherchables par le même chargeur de classe, et que vous voulez savoir laquelle d'entre elles vous chargez, vous ne pouvez inspecter que la façon dont ce chargeur de classe spécifique tente de résoudre un nom de classe.

  • Selon la spécification du langage Java, il n'y a pas de contrainte d'unicité pour un nom binaire de classe, mais pour autant que je sache, il devrait être unique pour chaque chargeur de classe.

Je peux trouver un moyen de charger deux classes avec le même nom binaire, et cela implique de les charger (et toutes leurs dépendances) par deux chargeurs de classe différents remplaçant le comportement par défaut. Un exemple approximatif:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

J'ai toujours trouvé que la personnalisation du chargeur de classe était une tâche délicate. Je suggère plutôt d'éviter plusieurs dépendances incompatibles si possible.

Luca Putzu
la source
14
Le chargeur de classe d'amorçage délègue au fichier approprié. Lorsque vous instanciez une nouvelle classe, le chargeur de classe plus spécifique est appelé. S'il ne trouve pas de référence à la classe que vous essayez de charger, il la délègue à son parent Merci de rester fidèle à moi, mais cela dépend de la politique de chargeur de classe qui est par défaut Parent First. En d'autres termes, la classe enfant demandera d'abord à son parent de charger la classe et ne se chargera que si toute la hiérarchie n'a pas réussi à la charger, non ??
deckingraj
5
Non - généralement un chargeur de classe délègue à son parent avant de rechercher la classe elle-même. Voir la classe javadoc pour Classloader.
Joe Kearney
1
Je pense que Tomcat le fait de la manière décrite ici, mais la délégation "conventionnelle" consiste à demander d'abord au parent
rogerdpack
@deckingraj: après quelques recherches sur Google, j'ai trouvé ceci à partir de la documentation oracle: "Dans la conception de la délégation, un chargeur de classe délègue le chargement de classe à son parent avant d' essayer de charger une classe lui-même. [...] Si le chargeur de classe parent ne peut pas charger une classe, le chargeur de classe tente de charger la classe elle-même. En effet, un chargeur de classe est responsable du chargement uniquement des classes non disponibles pour le parent ". Je vais enquêter plus loin. Si cela apparaît comme l'implémentation par défaut, je mettrai à jour la réponse en conséquence. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu
20

Chaque charge de classe sélectionne exactement une classe. Habituellement le premier trouvé.

OSGi vise à résoudre le problème de plusieurs versions d'un même fichier jar. Equinox et Apache Felix sont les implémentations open source courantes pour OSGi.

Tarlog
la source
6

Classloader chargera les classes à partir du fichier jar qui se trouvait en premier dans le chemin de classe. Normalement, les versions incompatibles de la bibliothèque auront des différences dans les packages, mais dans le cas peu probable, elles sont vraiment incompatibles et ne peuvent pas être remplacées par une seule - essayez jarjar.

Alex Gitelman
la source
6

Les chargeurs de classe chargent la classe à la demande. Cela signifie que la classe requise en premier par votre application et les bibliothèques associées seraient chargées avant les autres classes; la demande de chargement des classes dépendantes est généralement émise pendant le processus de chargement et de liaison d'une classe dépendante.

Vous êtes susceptible de rencontrer des LinkageErroraffirmations indiquant que des définitions de classe en double ont été rencontrées pour les chargeurs de classe n'essaient généralement pas de déterminer quelle classe doit être chargée en premier (s'il y a deux classes ou plus du même nom présentes dans le chemin de classe du chargeur). Parfois, le chargeur de classe chargera la première classe apparaissant dans le chemin de classe et ignorera les classes en double, mais cela dépend de l'implémentation du chargeur.

La pratique recommandée pour résoudre ce type d'erreurs consiste à utiliser un chargeur de classe distinct pour chaque ensemble de bibliothèques ayant des dépendances conflictuelles. De cette façon, si un chargeur de classe tente de charger des classes à partir d'une bibliothèque, les classes dépendantes seront chargées par le même chargeur de classe qui n'a pas accès aux autres bibliothèques et dépendances.

Vineet Reynolds
la source
1

Vous pouvez utiliser URLClassLoaderfor require pour charger les classes à partir d'une version diff-2 de jars:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();
Pankaj Kalra
la source