progressbar - progress bar java thread
¿Cómo debo cargar Jars dinámicamente en tiempo de ejecución? (15)
¿Qué tal el marco del cargador de clases JCL ? 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");
¿Por qué es tan difícil hacer esto en Java? Si desea tener cualquier tipo de sistema de módulos, necesita poder cargar archivos dinámicamente. Me dicen que hay una forma de hacerlo escribiendo su propio ClassLoader
, pero eso es mucho trabajo para algo que debería (en mi opinión al menos) ser tan fácil como llamar a un método con un archivo jar como su argumento.
¿Alguna sugerencia para el código simple que hace esto?
Aquí hay una solución rápida para el método de Allain para que sea 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 JVM específica, por lo que no es ideal y no es una solución universal. Pero es una solución rápida y fácil si sabe que va a utilizar OpenJDK estándar o Oracle JVM. También podría romperse en algún momento en el futuro cuando se lance la nueva versión de JVM, por lo que debe tenerlo en cuenta.
Aquí hay 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 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 utilizados han cambiado. En su lugar, para agregar al cargador de clases del sistema, puede utilizar la API de Instrumentation 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));
}
}
Agregue META-INF / MANIFEST.MF y póngalo en un archivo JAR con la clase de agente:
Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent
Ejecutar el agente:
Esto utiliza la biblioteca byte-buddy-agent 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());
}
}
Debería echar un vistazo a OSGi , por ejemplo, implementado en la plataforma Eclipse . Hace exactamente eso. Puede instalar, desinstalar, iniciar y detener los denominados paquetes, que en realidad son archivos JAR. Pero hace un poco más, ya que ofrece, por ejemplo, servicios que pueden descubrirse dinámicamente en archivos JAR en tiempo de ejecución.
O vea la especificación para el Sistema de Módulo Java .
Esta puede ser una respuesta tardía, puedo hacerlo así (un ejemplo simple para fastutil-8.2.2.jar) usando la clase 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
De acuerdo con la documentación, este archivo se descargará dentro de "lib / user" y luego se cargará dinámicamente, por lo que puede comenzar a usar inmediatamente las clases de este archivo jar en el mismo programa.
La razón por la que es difícil es la seguridad. Los cargadores de clases están destinados a ser inmutables; no debería ser capaz de agregarle clases en el tiempo de ejecución. Estoy realmente muy sorprendido de que funcione con el cargador de clases del sistema. Así es como lo haces haciendo tu propio cargador de clases para niños:
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á.
La siguiente solución es complicada, 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);
La solución propuesta por jodonnell es buena, pero debería mejorar un poco. Utilicé 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) almacenados en el contenedor.
No incluye
sus tarros en el cargador de clases padre o no podrá entender quién está cargando qué.
vea también Problema al recargar un tarro usando URLClassLoader
Sin embargo, el framework OSGi sigue siendo la mejor manera.
Lo mejor que he encontrado es org.apache.xbean.classloader.JarFileClassLoader que es parte del proyecto XBean .
Aquí hay un método corto que he usado en el pasado, para crear un cargador de clases 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());
}
Luego, para usar el cargador de clases, simplemente haz:
classLoader.loadClass(name);
Otra solución de trabajo con instrumentación que funciona para mí. Tiene la ventaja de modificar la búsqueda del cargador de clases, evitando problemas en la visibilidad de clases para las clases dependientes:
Crear una clase de agente
Para este ejemplo, tiene que estar en el mismo contenedor invocado por la línea de 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);
}
}
}
Modificar el MANIFEST.MF
Añadiendo la referencia al agente:
Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent
En realidad uso Netbeans, así que esta publicación ayuda a cambiar el archivo manifest.mf.
Corriendo
Launcher-Agent-Class
solo se admite en JDK 9+ y es responsable de cargar el agente sin definirlo explícitamente en la línea de comandos:
java -jar <your jar>
La forma en que funciona en JDK 6+ es definir el argumento -javaagent
:
java -javaagent:<your jar> -jar <your jar>
Añadiendo un nuevo Jar en Runtime
A continuación, puede agregar jar según sea necesario con el siguiente comando:
Agent.appendJarFile(new JarFile(<your file>));
No encontré ningún problema al usar esto en la documentación.
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.
Personalmente encuentro que java.util.ServiceLoader hace el trabajo bastante bien. Puedes obtener un ejemplo here .
Si está 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");
por favor, eche un vistazo a este proyecto que comencé: proxy-object lib
Esta biblioteca 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 biblioteca. Los usuarios podrán crear cualquier objeto desde el jar cargado y llamar a cualquier método en él. Esta biblioteca fue diseñada para cargar jars compilados en Java 8 desde 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 admite los métodos de fábrica, las funciones estáticas de llamada y las implementaciones de interfaz de devolución de llamada. Estaré publicando más ejemplos en la página Léame.