¿Por qué es tan difícil hacer esto en Java? Si desea tener algún tipo de sistema de módulos, debe poder cargar archivos JAR dinámicamente. Me han dicho que hay una manera de hacerlo escribiendo la suya ClassLoader
, pero eso es mucho trabajo para algo que (al menos en mi opinión) debería ser tan fácil como llamar a un método con un archivo JAR como argumento.
¿Alguna sugerencia para el código simple que hace esto?
java
jar
classloader
Allain Lalonde
fuente
fuente
Respuestas:
La razón por la que es difícil es la seguridad. Los cargadores de clases están destinados a ser inmutables; no deberías poder agregarle clases en tiempo de ejecución. De hecho, estoy muy sorprendido de que funcione con el cargador de clases del sistema. Así es como lo haces haciendo tu propio cargador de clases hijo:
Doloroso, pero ahí está.
fuente
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
suponer que se llama al archivo jarmy.jar
y se encuentra en el mismo directorio.La siguiente solución es agresiva, ya que utiliza la reflexión para evitar la encapsulación, pero funciona a la perfección:
fuente
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
es un uso ilegal de la reflexión y fallará en el futuro.Debe 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ódulo Java .
fuente
¿Qué tal el marco del cargador de clases JCL ? Tengo que admitir que no lo he usado, pero parece prometedor.
Ejemplo de uso:
fuente
Aquí hay una versión que no está en desuso. Modifiqué el original para eliminar la funcionalidad obsoleta.
fuente
Si bien la mayoría de las soluciones enumeradas aquí son hacks (anteriores a JDK 9) difíciles de configurar (agentes) o simplemente ya no funcionan (después de JDK 9), encuentro realmente impactante que nadie mencione un método claramente documentado .
Puede crear un cargador de clases de sistema personalizado y luego puede hacer lo que desee. No se requiere reflexión y todas las clases comparten el mismo cargador de clases.
Al iniciar la JVM, agregue esta bandera:
El cargador de clases debe tener un constructor que acepte un cargador de clases, que debe establecerse como padre. Se llamará al constructor en el inicio de JVM y se pasará el cargador de clases del sistema real, el cargador personalizado cargará la clase principal.
Para agregar frascos simplemente llame
ClassLoader.getSystemClassLoader()
y eche a su clase.Echa un vistazo a esta implementación para un cargador de clases cuidadosamente diseñado. Tenga en cuenta que puede cambiar el
add()
método a público.fuente
Con Java 9 , las respuestas con
URLClassLoader
ahora dan un error como:Esto se debe a que los cargadores de clases utilizados han cambiado. En cambio, para agregar al cargador de clases del sistema, puede usar la API de instrumentación a través de un agente.
Crear una clase de agente:
Agregue META-INF / MANIFEST.MF y póngalo en un archivo JAR con la clase de agente:
Ejecute el agente:
Utiliza la biblioteca byte-buddy-agent para agregar el agente a la JVM en ejecución:
fuente
Lo mejor que he encontrado es org.apache.xbean.classloader.JarFileClassLoader, que forma parte del proyecto XBean .
Aquí hay un método breve que he usado en el pasado, para crear un cargador de clases a partir de todos los archivos lib en un directorio específico
Luego, para usar el cargador de clases, simplemente haga:
fuente
Si está trabajando en Android, funciona el siguiente código:
fuente
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:
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 u Oracle JVM estándar. 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.
fuente
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
La solución propuesta por jodonnell es buena pero debería mejorarse un poco. Usé esta publicación para desarrollar mi aplicación con éxito.
Asignar el hilo actual
Primero tenemos que agregar
o no podrá cargar recursos (como spring / context.xml) almacenados en el jar.
No incluye
sus jarras en el cargador de clases principal o no podrá comprender quién está cargando qué.
vea también Problema al recargar un jar usando URLClassLoader
Sin embargo, el marco OSGi sigue siendo la mejor manera.
fuente
Otra versión de la solución pirata de Allain, que también funciona en JDK 11:
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.
fuente
Otra solución de trabajo usando Instrumentación que funciona para mí. Tiene la ventaja de modificar la búsqueda del cargador de clases, evitando problemas en la visibilidad de las clases dependientes:
Crear una clase de agente
Para este ejemplo, tiene que estar en el mismo jar invocado por la línea de comando:
Modificar el MANIFEST.MF
Agregar la referencia al agente:
De hecho, uso Netbeans, por lo que esta publicación me ayuda a cambiar el manifiesto.
Corriendo
El
Launcher-Agent-Class
solo es compatible con JDK 9+ y es responsable de cargar el agente sin definirlo explícitamente en la línea de comando:La forma en que funciona en JDK 6+ es definir el
-javaagent
argumento:Agregar nuevo Jar en tiempo de ejecución
Luego puede agregar jar según sea necesario con el siguiente comando:
No encontré ningún problema al usar esto en la documentación.
fuente
En caso de que alguien busque esto en el futuro, de esta manera me funciona con OpenJDK 13.0.2.
Tengo muchas clases que necesito instanciar dinámicamente en tiempo de ejecución, cada una potencialmente con un classpath diferente.
En este código, ya tengo un objeto llamado paquete, que contiene algunos metadatos sobre la clase que estoy tratando de cargar. El método getObjectFile () devuelve la ubicación del archivo de clase para la clase. El método getObjectRootPath () devuelve la ruta al directorio bin / que contiene los archivos de clase que incluyen la clase que estoy tratando de instanciar. El método getLibPath () devuelve la ruta a un directorio que contiene los archivos jar que constituyen la ruta de clase para el módulo del que forma parte la clase.
Estaba usando la dependencia Maven: org.xeustechnologies: jcl-core: 2.8 para hacer esto antes, pero después de pasar JDK 1.8, a veces se congeló y nunca regresó atascado "esperando referencias" en Reference :: waitForReferencePendingList ().
También mantengo un mapa de cargadores de clases para que puedan reutilizarse si la clase que estoy tratando de instanciar está en el mismo módulo que una clase que ya he instanciado, lo cual recomendaría.
fuente
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 lib fue diseñada para cargar frascos compilados en Java 8 desde la base de código que admite Java 7.
Para crear un objeto:
ObjectBuilder 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 Léame.
fuente
Esto 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 )
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.
fuente
Necesitaba cargar un archivo jar en tiempo de ejecución para java 8 y java 9+ (los comentarios anteriores no funcionan para ambas versiones). Aquí está el método para hacerlo (usando Spring Boot 1.5.2 si puede relacionarse).
fuente
Personalmente encuentro que java.util.ServiceLoader hace el trabajo bastante bien. Puedes obtener un ejemplo aquí .
fuente