Come dovrei caricare Jars dynamicmente in fase di runtime?

Perché è così difficile farlo in Java? Se vuoi avere qualsiasi tipo di sistema di moduli devi essere in grado di caricare i jar dynamicmente. Mi è stato detto che c’è un modo per farlo scrivendo il proprio ClassLoader , ma è molto lavoro per qualcosa che dovrebbe (almeno nella mia mente) essere facile come chiamare un metodo con un file jar come argomento.

Qualche suggerimento per codice semplice che fa questo?

Il motivo per cui è difficile è la sicurezza. I classloader sono pensati per essere immutabili; non dovresti essere in grado di aggiungere o aggiungere classi al runtime. Sono davvero molto sorpreso che funzioni con il classloader di sistema. Ecco come lo fai facendo il tuo classloader bambino:

 URLClassLoader child = new URLClassLoader(myJar.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); 

Doloroso, ma è così.

La seguente soluzione è hacker, in quanto utilizza la riflessione per bypassare l’incapsulamento, ma funziona perfettamente:

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

Dovresti dare un’occhiata a OSGi , ad esempio implementato nella piattaforma Eclipse . Lo fa esattamente. È ansible installare, disinstallare, avviare e interrompere i cosiddetti pacchetti, che sono effettivamente file JAR. Ma fa un po ‘di più, in quanto offre ad esempio servizi che possono essere scoperti dynamicmente in file JAR in fase di runtime.

Oppure vedi le specifiche per il Java Module System .

Che ne dici del framework del caricatore di classi JCL ? Devo ammettere che non l’ho usato, ma sembra promettente.

Esempio di utilizzo:

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

Ecco una versione che non è deprecata. Ho modificato l’originale per rimuovere la funzionalità deprecata.

 /************************************************************************************************** * 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 classs during runtime. */ public class ClasspathHacker { /** * Parameters of the method to add an URL to the System classs. */ private static final Class[] parameters = new Class[]{URL.class}; /** * Adds a file to the classpath. * @param sa 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(); } } 

Il migliore che ho trovato è org.apache.xbean.classloader.JarFileClassLoader che fa parte del progetto XBean .

Ecco un breve metodo che ho usato in passato per creare un programma di caricamento class da tutti i file lib in una directory specifica

 public void initialize(String libDir) throws Exception { File dependencyDirectory = new File(libDir); File[] files = dependencyDirectory.listFiles(); ArrayList urls = new ArrayList(); 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()); } 

Quindi per usare il classloader, basta fare:

 classLoader.loadClass(name); 

Le risposte con URLClassLoader non funzionano più in Java 9, perché i programmi di URLClassLoader class utilizzati sono stati modificati.

Per aggiungere al caricatore di class di sistema, è ansible utilizzare l’API di strumentazione tramite un agente.

Crea una class agente:

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

Aggiungi META-INF / MANIFEST.MF e inseriscilo in un file JAR con la class agent:

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

Esegui agente. Questo utilizza la libreria byte-buddy-agent per aggiungere l’agente alla JVM in esecuzione:

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

Se stai lavorando su Android, il seguente codice funziona:

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

La soluzione proposta da jodonnell è buona, ma dovrebbe essere un po ‘migliorata. Ho usato questo post per sviluppare la mia domanda con successo.

Assegna il thread corrente

Per prima cosa dobbiamo aggiungere

 Thread.currentThread().setContextClassLoader(classLoader); 

o non sarai in grado di caricare risorse (come spring / context.xml) memorizzate nel jar.

Non comprendono

i tuoi vasi nel caricatore della class genitore o non sarai in grado di capire chi sta caricando cosa.

vedere anche Problema di ricarica di un jar usando URLClassLoader

Tuttavia, il framework OSGi rimane il modo migliore.

per favore guarda questo progetto che ho iniziato: proxy-object lib

Questa libreria caricherà jar dal file system o da qualsiasi altra posizione. Dedicherà un caricatore di classi per il jar per assicurarsi che non ci siano conflitti di libreria. Gli utenti saranno in grado di creare qualsiasi object dal barattolo caricato e chiamare qualsiasi metodo su di esso. Questa libreria è stata progettata per caricare i file compilati in Java 8 dalla base di codice che supporta Java 7.

Per creare un object:

 ProxyCallerInterface object = ObjectBuilder.builder() .setPackageName("org.my.package") .setClassName("MyClass") .setVersionInfo(newVersionInfo("myLib", "2.0")) .build(); object.call("objectMethod"); 

ObjectBuilder supporta i metodi di fabbrica, le funzioni statiche e le implementazioni dell’interfaccia di richiamata. posterò altri esempi sulla pagina readme.

Personalmente trovo che java.util.ServiceLoader faccia il lavoro abbastanza bene. Puoi fare un esempio qui .