¿Cómo debo cargar Jars dinámicamente en tiempo de ejecución?


¿Por qué es tan difícil hacer esto en Java? Si quieres tener cualquier tipo de sistema de módulos necesitas poder cargar jars dinámicamente. Me han dicho que hay una manera de hacerlo escribiendo su propio ClassLoader, pero eso es mucho trabajo para algo que debería (al menos en mi mente) ser tan fácil como llamar a un método con un archivo jar como argumento.

¿Alguna sugerencia de código simple que haga esto?

Author: Francesco Menzani, 2008-09-13

12 answers

La razón por la que es difícil es la seguridad. Los Classloaders están destinados a ser inmutables; no debería ser capaz de agregarle clases en tiempo de ejecución. En realidad estoy muy sorprendido de que funciona con el sistema classloader. Así es como lo haces haciendo tu propio hijo classloader:

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, pero ahí está.

 196
Author: jodonnell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-16 21:46:19

La siguiente solución es hackish, ya que utiliza la reflexión para evitar la encapsulación, pero funciona perfectamente:

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);
 124
Author: Allain Lalonde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-19 14:40:08

Deberías echar un vistazo a OSGi, por ejemplo implementado en la Plataforma Eclipse. Hace exactamente eso. Puede instalar, desinstalar, iniciar y detener los llamados paquetes, que son efectivamente archivos JAR. Pero hace un poco más, ya que ofrece, por ejemplo, servicios que se pueden descubrir dinámicamente en archivos JAR en tiempo de ejecución.

O vea la especificación para el Sistema de Módulos Java .

 46
Author: Martin Klinke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-13 18:55:34

¿Qué tal el framework JCL class loader? Tengo que admitir que no lo he usado, pero parece prometedor.

Ejemplo de uso:

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");
 40
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-17 15:06:20

Aquí hay una versión que no está obsoleta. He modificado el original para eliminar la funcionalidad obsoleta.

/**************************************************************************************************
 * 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();
    }
}
 21
Author: Jonathan Nadeau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-10 17:05:14

Lo mejor que he encontrado es org.apache.xbean.classloader.JarFileClassLoader que forma parte del proyecto XBean.

Aquí hay un método corto que he usado en el pasado, para crear un cargador de clases a partir de todos los archivos lib en un directorio específico

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

Entonces para usar el classloader, simplemente haga:

classLoader.loadClass(name);
 8
Author: Zeusoflightning125,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-04 21:45:57

Con Java 9 , las respuestas con URLClassLoader ahora dan un error como:

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

Esto se debe a que los cargadores de clases usados han cambiado. En su lugar, para agregar al cargador de clases del sistema, puede usar la API Instrumentation a través de un agente.

Crear una clase de 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));
    }
}

Añadir META-INF / MANIFEST.MF y ponerlo en un archivo JAR con la clase agent:

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

Ejecute el agente:

Esto utiliza el byte-buddy-agent biblioteca para agregar el agente a la JVM en ejecución:

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());
    }
}
 8
Author: fgb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-29 11:50:18

Si estás trabajando en Android, el siguiente código funciona:

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");
 6
Author: Caner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-15 12:57:44

La solución propuesta por jodonnell es buena, pero debería ser un poco mejorada. He utilizado este post para desarrollar mi aplicación con éxito.

Asignar el hilo actual

En primer lugar tenemos que añadir

Thread.currentThread().setContextClassLoader(classLoader);

O no podrá cargar recursos (como spring/context.xml) almacenado en el jar.

No incluye

Sus jars en el cargador de clases padre o no podrá entender quién está cargando qué.

Véase también Problema de recarga un jar usando URLClassLoader

Sin embargo, OSGi framework sigue siendo la mejor manera.

 3
Author: venergiac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:45

Otra versión de la solución hackish de Allain, que también funciona en 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});

En JDK 11 da algunas advertencias de desaprobación, pero sirve como una solución temporal para aquellos que usan la solución Allain en JDK 11.

 2
Author: czdepski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-02 21:58:21

Por favor, echa un vistazo a este proyecto que empecé: proxy-object lib

Esta lib cargará jar desde el sistema de archivos o cualquier otra ubicación. Dedicará un cargador de clases para el jar para asegurarse de que no haya conflictos de bibliotecas. Los usuarios podrán crear cualquier objeto desde el jar cargado y llamar a cualquier método en él. Esta lib fue diseñada para cargar jars compilados en Java 8 a partir del código base que soporta Java 7.

Para crear un objeto:

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

Constructor de objetos admite métodos de fábrica, llamadas a funciones estáticas e implementaciones de interfaz de devolución de llamada. publicaré más ejemplos en la página readme.

 0
Author: Aleksey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-21 00:24:43

Personalmente encuentro que java.útil.ServiceLoader hace el trabajo bastante bien. Puede obtener un ejemplo aquí.

 -2
Author: tanyehzheng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-04 21:46:19