web-dev-qa-db-fra.com

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

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

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

Le classloader est-il assez intelligent pour les séparer? Préférablement pas? Comment le chargeur de classe gère-t-il cela, au cas où une classe serait la même dans les trois jars? Lequel est chargé et pourquoi?

Est-ce que Classloader ramasse exactement un seul pot ou mélange-t-il les classes de façon arbitraire? Ainsi, par exemple, si une classe est chargée à partir de la version 1.jar, toutes les autres classes chargées à partir du même chargeur de classes iront toutes dans le même fichier jar?

Comment gérez-vous ce problème?

Existe-t-il une astuce pour "incorporer" les fichiers jar dans le fichier "required.jar" de sorte qu'ils soient vus comme "une unité/package" par le Classloader, ou en quelque sorte liés?

112
jens

Les problèmes liés aux chargeurs de classes sont une affaire assez complexe. Dans tous les cas, gardez à l’esprit certains faits:

  • Les chargeurs de classes dans une application sont généralement plus d'un seul. Les délégués du chargeur de classe bootstrap) sont appropriés. Lorsque vous instanciez une nouvelle classe, le chargeur de classes plus spécifique est appelé. S'il ne trouve pas de référence à la classe que vous essayez de charger, il délègue son parent, et ainsi de suite, jusqu’à ce que vous obteniez le chargeur de classe bootstrap). Si aucun d’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, interrogeables par le même chargeur de classes et que vous voulez savoir laquelle d'entre elles est chargée, vous pouvez uniquement inspecter la façon dont le chargeur de classes 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, 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 (avec 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érerais plutôt d'éviter autant que possible les dépendances incompatibles.

52
Luca Putzu

Chaque classe charge choisit exactement une classe. Habituellement, le premier trouvé.

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

20
Tarlog

Classloader chargera les classes à partir du fichier jar qui se trouvait dans le classpath en premier. Normalement, les versions incompatibles de la bibliothèque auront une différence dans les paquets, mais dans des cas peu probables, elles seront vraiment incompatibles et ne pourront pas être remplacées par un - essayez jarjar.

6
Alex Gitelman

Classloaders charge 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 rencontrerez probablement LinkageErrors indiquant que des définitions de classe en double ont été rencontrées pour les chargeurs de classe. En règle générale, vous ne tentez pas de déterminer quelle classe doit être chargée en premier (s'il existe au moins deux classes du même nom dans le chemin de classe de le chargeur). Parfois, le chargeur de classes chargera la première classe apparaissant dans le chemin de classes 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'erreur consiste à utiliser un chargeur de classe distinct pour chaque ensemble de bibliothèques ayant des dépendances en conflit. Ainsi, si un chargeur de classes tente de charger des classes à partir d'une bibliothèque, les classes dépendantes seraient chargées par le même chargeur de classes qui n'a pas accès aux autres bibliothèques et dépendances.

6
Vineet Reynolds

Vous pouvez utiliser le URLClassLoader for 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();
0
Pankaj Kalra