Question

Pourquoi est-il si difficile de faire cela en Java ?Si vous souhaitez disposer de tout type de système de modules, vous devez pouvoir charger des fichiers jar de manière dynamique.On m'a dit qu'il y avait un moyen de le faire en écrivant le vôtre ClassLoader, mais cela demande beaucoup de travail pour quelque chose qui devrait (du moins dans mon esprit) être aussi simple que d'appeler une méthode avec un fichier jar comme argument.

Des suggestions pour un code simple qui fait cela ?

Était-ce utile?

La solution

La raison pour laquelle c'est difficile est la sécurité.Les classloaders sont censés être immuables ;vous ne devriez pas pouvoir y ajouter bon gré mal gré des classes au moment de l'exécution.Je suis en fait très surpris que cela fonctionne avec le chargeur de classes système.Voici comment procéder en créant votre propre chargeur de classe enfant :

URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

C'est douloureux, mais c'est là.

Autres conseils

La solution suivante est hackish, car elle utilise la réflexion pour contourner l’encapsulation, mais elle fonctionne parfaitement :

File file = ...
URL url = file.toURI().toURL();

URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);

Tu devrais jeter un oeil à OSGi, par exemple.mis en œuvre dans le Plateforme Éclipse.C'est exactement ce que fait.Vous pouvez installer, désinstaller, démarrer et arrêter ce qu'on appelle des bundles, qui sont en réalité des fichiers JAR.Mais il fait un peu plus, puisqu'il propose par ex.services qui peuvent être découverts dynamiquement dans les fichiers JAR au moment de l'exécution.

Ou consultez les spécifications pour le Système de modules Java.

Que diriez-vous du Cadre de chargement de classes JCL?Je dois admettre que je ne l'ai pas utilisé, mais cela semble prometteur.

Exemple d'utilisation :

JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file  
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder  
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)

JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class  
Object obj = factory.create(jcl, "mypackage.MyClass");

Voici une version qui n'est pas obsolète.J'ai modifié l'original pour supprimer la fonctionnalité obsolète.

/**************************************************************************************************
 * Copyright (c) 2004, Federal University of So Carlos                                           *
 *                                                                                                *
 * All rights reserved.                                                                           *
 *                                                                                                *
 * Redistribution and use in source and binary forms, with or without modification, are permitted *
 * provided that the following conditions are met:                                                *
 *                                                                                                *
 *     * Redistributions of source code must retain the above copyright notice, this list of      *
 *       conditions and the following disclaimer.                                                 *
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
 *     * conditions and the following disclaimer in the documentation and/or other materials      *
 *     * provided with the distribution.                                                          *
 *     * Neither the name of the Federal University of So Carlos nor the names of its            *
 *     * contributors may be used to endorse or promote products derived from this software       *
 *     * without specific prior written permission.                                               *
 *                                                                                                *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 **************************************************************************************************/
/*
 * Created on Oct 6, 2004
 */
package tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Useful class for dynamically changing the classpath, adding classes during runtime. 
 */
public class ClasspathHacker {
    /**
     * Parameters of the method to add an URL to the System classes. 
     */
    private static final Class<?>[] parameters = new Class[]{URL.class};

    /**
     * Adds a file to the classpath.
     * @param s a String pointing to the file
     * @throws IOException
     */
    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }

    /**
     * Adds a file to the classpath
     * @param f the file to be added
     * @throws IOException
     */
    public static void addFile(File f) throws IOException {
        addURL(f.toURI().toURL());
    }

    /**
     * Adds the content pointed by the URL to the classpath.
     * @param u the URL pointing to the content to be added
     * @throws IOException
     */
    public static void addURL(URL u) throws IOException {
        URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        Class<?> sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL",parameters);
            method.setAccessible(true);
            method.invoke(sysloader,new Object[]{ u }); 
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }        
    }

    public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        addFile("C:\\dynamicloading.jar");
        Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
        DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
        instance.test();
    }
}

Avec Java9, les réponses avec URLClassLoader donne maintenant une erreur du genre :

java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

C'est parce que les chargeurs de classes utilisés ont changé.Au lieu de cela, pour ajouter au chargeur de classes système, vous pouvez utiliser le Instrumentation API via un agent.

Créez une classe d'agent :

package ClassPathAgent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class ClassPathAgent {
    public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
        instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
    }
}

Ajoutez META-INF/MANIFEST.MF et placez-le dans un fichier JAR avec la classe d'agent :

Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent

Exécutez l'agent :

Cela utilise le octet-buddy-agent bibliothèque pour ajouter l'agent à la JVM en cours d'exécution :

import java.io.File;

import net.bytebuddy.agent.ByteBuddyAgent;

public class ClassPathUtil {
    private static File AGENT_JAR = new File("/path/to/agent.jar");

    public static void addJarToClassPath(File jarFile) {
        ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
    }
}

Le meilleur que j'ai trouvé est org.apache.xbean.classloader.JarFileClassLoader qui fait partie du XBean projet.

Voici une courte méthode que j'ai utilisée dans le passé pour créer un chargeur de classe à partir de tous les fichiers lib d'un répertoire spécifique.

public void initialize(String libDir) throws Exception {
    File dependencyDirectory = new File(libDir);
    File[] files = dependencyDirectory.listFiles();
    ArrayList<URL> urls = new ArrayList<URL>();
    for (int i = 0; i < files.length; i++) {
        if (files[i].getName().endsWith(".jar")) {
        urls.add(files[i].toURL());
        //urls.add(files[i].toURI().toURL());
        }
    }
    classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), 
        urls.toArray(new URL[urls.size()]), 
        GFClassLoader.class.getClassLoader());
}

Ensuite, pour utiliser le classloader, il suffit de faire :

classLoader.loadClass(name);

Si vous travaillez sous Android, le code suivant fonctionne :

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

La solution proposée par Jodonnell est bonne mais devrait être un peu améliorée.J'ai utilisé ce post pour développer mon application avec succès.

Attribuer le fil de discussion actuel

Il faut d'abord ajouter

Thread.currentThread().setContextClassLoader(classLoader);

ou vous ne pourrez pas charger les ressources (telles que spring/context.xml) stockées dans le pot.

N'incluez pas

vos pots dans le chargeur de classe parent ou vous ne pourrez pas comprendre qui charge quoi.

voir également Problème lors du rechargement d'un fichier jar à l'aide d'URLClassLoader

Cependant, le framework OSGi reste la meilleure solution.

Une autre version de la solution hackish d'Allain, qui fonctionne également sur JDK 11 :

File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});

Sur JDK 11, il donne quelques avertissements de dépréciation mais sert de solution temporaire à ceux qui utilisent la solution Allain sur JDK 11.

Une autre solution de travail utilisant l'instrumentation qui fonctionne pour moi.Il a l'avantage de modifier la recherche du chargeur de classe, évitant ainsi les problèmes de visibilité des classes dépendantes :

Créer une classe d'agent

Pour cet exemple, il doit se trouver sur le même fichier jar invoqué par la ligne de commande :

package agent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class Agent {
   public static Instrumentation instrumentation;

   public static void premain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void agentmain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void appendJarFile(JarFile file) throws IOException {
      if (instrumentation != null) {
         instrumentation.appendToSystemClassLoaderSearch(file);
      }
   }
}

Modifier le MANIFEST.MF

Ajout de la référence à l'agent :

Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent

En fait, j'utilise Netbeans, donc ce post aide sur la façon de changer le manifest.mf

En cours d'exécution

Le Launcher-Agent-Class n'est pris en charge que sur JDK 9+ et est responsable du chargement de l'agent sans le définir explicitement sur la ligne de commande :

 java -jar <your jar>

La façon dont cela fonctionne sur JDK 6+ consiste à définir le -javaagent argument:

java -javaagent:<your jar> -jar <your jar>

Ajout d'un nouveau pot au moment de l'exécution

Vous pouvez ensuite ajouter du jar si nécessaire à l'aide de la commande suivante :

Agent.appendJarFile(new JarFile(<your file>));

Je n'ai trouvé aucun problème à l'utiliser dans la documentation.

Voici une solution rapide pour la méthode d'Allain afin de la rendre compatible avec les versions plus récentes de Java :

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
    Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
    Method method = classLoader.getClass()
            .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    method.invoke(classLoader, jarPath);
}

Notez que cela repose sur la connaissance de l’implémentation interne d’une JVM spécifique, ce n’est donc pas idéal et ce n’est pas une solution universelle.Mais c'est une solution de contournement rapide et simple si vous savez que vous allez utiliser OpenJDK standard ou Oracle JVM.Il pourrait également se briser à un moment donné dans le futur lors de la publication d'une nouvelle version de JVM, vous devez donc garder cela à l'esprit.

Cela peut être une réponse tardive, je peux le faire comme ceci (un exemple simple pour fastutil-8.2.2.jar) en utilisant la classe jhplot.Web de DataMelt (http://jwork.org/dmelt)

import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library

Selon la documentation, ce fichier sera téléchargé dans "lib/user" puis chargé dynamiquement, vous pourrez donc commencer immédiatement à utiliser les classes de ce fichier jar dans le même programme.

s'il vous plaît jetez un oeil à ce projet que j'ai commencé: bibliothèque d'objet proxy

Cette bibliothèque chargera le fichier jar à partir du système de fichiers ou de tout autre emplacement.Il consacrera un chargeur de classe au fichier jar pour s'assurer qu'il n'y a pas de conflits de bibliothèque.Les utilisateurs pourront créer n'importe quel objet à partir du fichier jar chargé et appeler n'importe quelle méthode dessus.Cette bibliothèque a été conçue pour charger des fichiers jar compilés en Java 8 à partir de la base de code prenant en charge Java 7.

Pour créer un objet :

    File libDir = new File("path/to/jar");

    ProxyCallerInterface caller = ObjectBuilder.builder()
            .setClassName("net.proxy.lib.test.LibClass")
            .setArtifact(DirArtifact.builder()
                    .withClazz(ObjectBuilderTest.class)
                    .withVersionInfo(newVersionInfo(libDir))
                    .build())
            .build();
    String version = caller.call("getLibVersion").asString();

ObjectBuilder prend en charge les méthodes de fabrique, l'appel de fonctions statiques et les implémentations d'interface de rappel.je publierai plus d'exemples sur la page Lisez-moi.

perso je trouve ça java.util.ServiceLoader fait plutôt bien le travail.Vous pouvez obtenir un exemple ici.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top