Pregunta

¿Por qué es tan difícil hacer esto en Java?Si usted quiere tener ningún tipo de módulo de sistema que usted necesita para ser capaz de cargar los frascos de forma dinámica.Me dicen que hay una manera de hacerlo por medio de la escritura de su propia ClassLoader, pero eso es mucho trabajo para algo que debería (en mi mente al menos) ser tan fácil como llamar a un método con un tarro de archivo como argumento.

Alguna sugerencia para simple código que hace esto?

¿Fue útil?

Solución

La razón por la que es difícil es la seguridad.Los cargadores de clases están pensadas para ser inmutable;usted no debe ser capaz de quiéralo o no agregar clases en tiempo de ejecución.En realidad estoy muy sorprendido de que funciona con el sistema del cargador de clases.Aquí te explicamos cómo hacerlo de hacer su propio hijo cargador de clases:

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

Otros consejos

La siguiente solución es hackish, ya que utiliza la reflexión para la derivación de encapsulación, pero funciona a la perfección:

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

Usted debe echar un vistazo a OSGi, por ejemplo,implementado en el La Plataforma Eclipse.Hace exactamente eso.Usted puede instalar, desinstalar, iniciar y detener los llamados paquetes, que son efectivamente los archivos JAR.Pero hace un poco más, ya que ofrece por ejemploservicios que pueden ser dinámicamente descubierto en los archivos JAR en tiempo de ejecución.

O vea la especificación de la Java Módulo De Sistema.

¿ JCL cargador de clases marco?Tengo que admitir, no lo he utilizado, 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");

Aquí es una versión que no está en desuso.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();
    }
}

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

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

Esto es debido a que la clase de cargadoras usadas han cambiado.En su lugar, para agregar a la clase de sistema de carga, puede utilizar la Instrumentación API 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 de agente:

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

Ejecutar el agente:

Para ello se utiliza el byte-buddy-agente biblioteca para agregar el agente para la ejecución de la JVM:

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

La mejor que he encontrado es org.apache.xbean.cargador de clases.JarFileClassLoader que es parte de la XBean proyecto.

He aquí un breve método que he utilizado en el pasado, para crear un cargador de clase 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());
}

A continuación, utilice el cargador de clases, acabo de hacer:

classLoader.loadClass(name);

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

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

La solución propuesta por jodonnell es buena, pero debe ser un poco mayor.He utilizado este post desarrollar mi aplicación con éxito.

Asignar el subproceso actual

En primer lugar tenemos que añadir

Thread.currentThread().setContextClassLoader(classLoader);

o usted no será capaz de cargar los recursos (tales como spring/context.xml) que se almacenan en el frasco.

No incluyen

los frascos en el cargador de clases padre o usted no será capaz de entender que es la carga de lo que.

ver también El problema de la recarga de un vaso con URLClassLoader

Sin embargo, OSGi marco sigue siendo la mejor manera.

Otra versión de la hackish solución de Allain, que también trabaja en el 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 el JDK 11 da un poco de amortización advertencias, sino que sirve como una solución temporal a aquellos que utilizan Allain solución en el JDK 11.

Otra solución de trabajo de la utilización de instrumentos que funciona para mí.Tiene la ventaja de modificar el cargador de clase de búsqueda, evitando problemas en la clase de visibilidad para las clases dependientes:

Crear una Clase de Agente

Para este ejemplo, tiene que ser en el mismo frasco invocada por la línea de comandos:

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

Modificar el MANIFIESTO.MF

Agregar la referencia al agente:

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

En realidad yo uso Netbeans, así que este post ayuda sobre cómo cambiar el manifiesto.mf

Ejecución

El Launcher-Agent-Class sólo es compatible con JDK 9+ y es el responsable de cargar el agente sin definir explícitamente en la línea de comandos:

 java -jar <your jar>

La forma en que funciona en JDK 6+ es la definición de la -javaagent argumento:

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

La adición de nuevos Jar en tiempo de ejecución

A continuación, puede agregar jar según sea necesario utilizando el siguiente comando:

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

No he podido encontrar ningún problema usando este sobre la documentación.

Aquí hay una rápida solución para Allain del método para hacerlo compatible con las versiones más recientes 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);
}

Tenga en cuenta que se basa en el conocimiento de la implementación interna de los específicos de la JVM, así que no es ideal, y no es una solución universal.Pero es una rápida y fácil solución si usted sabe que usted va a utilizar el estándar de OpenJDK o Oracle JVM.También podría romper en algún momento en el futuro cuando la nueva versión de la JVM es liberado, por lo que necesita para mantener eso en mente.

Esto puede ser una respuesta tardía, puedo hacerlo como este (un ejemplo simple de fastutil-8.2.2.jar) el uso de jhplot.Web de la clase 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

Según la documentación, el archivo se descarga en el interior de "lib/usuario" y, a continuación, cargar dinámicamente, de modo que usted puede comenzar inmediatamente con las clases a partir de este archivo jar en el mismo programa.

por favor, echa un vistazo a este proyecto que empecé: proxy-objeto lib

Este lib carga frasco de sistema de archivos o en cualquier otro lugar.Se dedicará una clase loader para el envase para asegurarse de que no hay conflictos de biblioteca.Los usuarios serán capaces de crear cualquier objeto de la carga frasco y llamar a cualquier método.Este lib fue diseñado para cargar los frascos compilado en Java 8 de la base de código 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();

ObjectBuilder soporta métodos de fábrica, llamando a las funciones estáticas, y volver a llamar implementaciones de interfaz.voy a publicar más ejemplos en la página readme.

Personalmente me parece que java.util.ServiceLoader hace el trabajo bastante bien.Usted puede obtener un ejemplo aquí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top