Domanda

Perché è così difficile farlo in Java?Se vuoi avere qualsiasi tipo di sistema di moduli devi essere in grado di caricare i jar in modo dinamico.Mi è stato detto che c'è un modo per farlo scrivendo il tuo 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 un codice semplice che faccia questo?

È stato utile?

Soluzione

Il motivo per cui è difficile è la sicurezza.I classloader sono pensati per essere immutabili;non dovresti essere in grado di aggiungere classi in fase di esecuzione, volenti o nolenti.In realtà sono molto sorpreso che funzioni con il classloader di sistema.Ecco come puoi farlo creando il tuo classloader figlio:

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

Doloroso, ma è così.

Altri suggerimenti

La seguente soluzione è hacker, poiché utilizza la riflessione per aggirare 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 OSGi, per esempio.implementato nel Piattaforma Eclipse.Fa esattamente questo.È possibile installare, disinstallare, avviare e arrestare i cosiddetti bundle, che sono effettivamente file JAR.Ma fa qualcosa di più, poiché offre ad es.servizi che possono essere rilevati dinamicamente nei file JAR in fase di runtime.

Oppure vedere le specifiche per Sistema di moduli Java.

Che ne dici di 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 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();
    }
}

Con Giava9, le risposte con URLClassLoader ora dai un errore come:

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

Questo perché i caricatori di classi utilizzati sono cambiati.Invece, per aggiungere al caricatore di classi del sistema, puoi usare il file Strumentazione API tramite un agente.

Crea una classe 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 classe agente:

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

Esegui l'agente:

Questo utilizza il byte-amico-agente libreria 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());
    }
}

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

Ecco un breve metodo che ho usato in passato per creare un caricatore di classi 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<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());
}

Quindi per utilizzare il classloader, basta fare:

classLoader.loadClass(name);

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 leggermente migliorata.Ho utilizzato questo post per sviluppare la mia applicazione con successo.

Assegnare il thread corrente

Innanzitutto dobbiamo aggiungere

Thread.currentThread().setContextClassLoader(classLoader);

oppure non sarai in grado di caricare la risorsa (come spring/context.xml) memorizzata nel jar.

Non comprendono

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

Guarda anche Problema durante il ricaricamento di un jar utilizzando URLClassLoader

Tuttavia, il framework OSGi rimane la soluzione migliore.

Un'altra versione della soluzione hacker di Allain, che funziona anche su 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});

Su JDK 11 fornisce alcuni avvisi di deprecazione ma serve come soluzione temporanea per coloro che utilizzano la soluzione Allain su JDK 11.

Un'altra soluzione funzionante utilizzando la strumentazione che funziona per me.Ha il vantaggio di modificare la ricerca del caricatore di classi, evitando problemi di visibilità delle classi dipendenti:

Crea una classe agente

Per questo esempio, deve trovarsi sullo stesso jar richiamato dalla riga di comando:

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

Modificare MANIFEST.MF

Aggiunta del riferimento all'agente:

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

In realtà utilizzo Netbeans, quindi questo post aiuta su come modificare manifest.mf

Corsa

IL Launcher-Agent-Class è supportato solo su JDK 9+ ed è responsabile del caricamento dell'agente senza definirlo esplicitamente sulla riga di comando:

 java -jar <your jar>

Il modo in cui funziona su JDK 6+ è definire il file -javaagent discussione:

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

Aggiunta di un nuovo Jar in fase di runtime

È quindi possibile aggiungere jar secondo necessità utilizzando il seguente comando:

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

Non ho riscontrato alcun problema nell'utilizzo di questo sulla documentazione.

Ecco una soluzione rapida per il metodo di Allain per renderlo compatibile con le versioni più recenti di 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);
}

Tieni presente che si basa sulla conoscenza dell'implementazione interna di JVM specifica, quindi non è l'ideale e non è una soluzione universale.Ma è una soluzione semplice e veloce se sai che utilizzerai OpenJDK o Oracle JVM standard.Potrebbe anche rompersi in futuro quando verrà rilasciata la nuova versione JVM, quindi è necessario tenerlo a mente.

Questa può essere una risposta tardiva, posso farlo in questo modo (un semplice esempio per fastutil-8.2.2.jar) utilizzando la classe jhplot.Web di 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

Secondo la documentazione, questo file verrà scaricato all'interno di "lib/user" e quindi caricato dinamicamente, in modo da poter iniziare immediatamente a utilizzare le classi da questo file jar nello stesso programma.

per favore dai un'occhiata a questo progetto che ho iniziato: libreria dell'oggetto proxy

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 potranno creare qualsiasi oggetto dal jar caricato e richiamare qualsiasi metodo su di esso.Questa libreria è stata progettata per caricare jar compilati in Java 8 dalla codebase che supporta Java 7.

Per creare un oggetto:

    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 supporta metodi factory, chiamate di funzioni statiche e implementazioni dell'interfaccia di callback.Pubblicherò altri esempi nella pagina Leggimi.

Personalmente lo trovo java.util.ServiceLoader fa il lavoro abbastanza bene.Puoi ottenere un esempio Qui.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top