Todavía soy relativamente nuevo en Java, así que por favor tengan paciencia conmigo.Jar hell: cómo usar un cargador de clases para reemplazar una versión de la biblioteca jar con otra en el tiempo de ejecución
Mi problema es que mi aplicación Java depende de dos bibliotecas. Vamos a llamar a la biblioteca 1 y biblioteca 2. Ambas bibliotecas comparten una dependencia mutua en la Biblioteca 3. Sin embargo:
- Biblioteca 1 requiere exactamente la versión 1 de la Biblioteca 3.
- Biblioteca 2 requiere exactamente la versión 2 de la Biblioteca 3.
Esta es exactamente la definición de JAR hell (o al menos una de sus variaciones). Como se indica en el enlace, no puedo cargar ambas versiones de la tercera biblioteca en el mismo cargador de clases. Por lo tanto, he estado tratando de averiguar si puedo crear un nuevo cargador de clases dentro de la aplicación para resolver este problema. He estado buscando en URLClassLoader, pero no he podido averiguarlo.
Aquí hay una estructura de aplicación de ejemplo que demuestra el problema. La clase principal (Main.java) de la aplicación intenta crear una instancia tanto Biblioteca1 y Biblioteca2 y ejecutar algún método definido en esas bibliotecas:
Main.java (versión original, antes de cualquier intento de solución):
public class Main {
public static void main(String[] args) {
Library1 lib1 = new Library1();
lib1.foo();
Library2 lib2 = new Library2();
lib2.bar();
}
}
Biblioteca1 y Biblioteca2 ambos comparten una dependencia mutua en Library3, pero Biblioteca1 requiere exactamente la versión 1, y Biblioteca2 requiere exactamente la versión 2. en el ejemplo, estas dos bibliotecas simplemente imprimir la versión de Library3 que ven:
Lib rary1.java:
public class Library1 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 1."
}
}
Library2.java:
public class Library2 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 2." if the correct version of Library3 is loaded.
}
}
Y luego, por supuesto, hay varias versiones de Library3. Lo único que hacen es imprimir sus números de versión:
Versión 1 de Library3 (requerido por Biblioteca1):
public class Library3 {
public void printVersion() {
System.out.println("This is version 1.");
}
}
versión 2 de Library3 (requerido por Biblioteca2):
public class Library3 {
public void printVersion() {
System.out.println("This is version 2.");
}
}
Cuando ejecuto la aplicación, el classpath contiene Library1 (lib1.jar), Library2 (lib2.jar) y la versión 1 de Library 3 (lib3-v1/lib3.jar). Esto funciona bien para Library1, pero no funcionará para Library2.
Lo que de alguna manera tengo que hacer es reemplazar la versión de Library3 que aparece en el classpath antes de crear una instancia de Library2. Tenía la impresión de que URLClassLoader podría usarse para esto, así que esto es lo que probé:
Principal.java (nueva versión, incluyendo mi intento de solución):
import java.net.*;
import java.io.*;
public class Main {
public static void main(String[] args)
throws MalformedURLException, ClassNotFoundException,
IllegalAccessException, InstantiationException,
FileNotFoundException
{
Library1 lib1 = new Library1();
lib1.foo(); // This causes "This is version 1." to print.
// Original code:
// Library2 lib2 = new Library2();
// lib2.bar();
// However, we need to replace Library 3 version 1, which is
// on the classpath, with Library 3 version 2 before attempting
// to instantiate Library2.
// Create a new classloader that has the version 2 jar
// of Library 3 in its list of jars.
URL lib2_url = new URL("file:lib2/lib2.jar"); verifyValidPath(lib2_url);
URL lib3_v2_url = new URL("file:lib3-v2/lib3.jar"); verifyValidPath(lib3_v2_url);
URL[] urls = new URL[] {lib2_url, lib3_v2_url};
URLClassLoader c = new URLClassLoader(urls);
// Try to instantiate Library2 with the new classloader
Class<?> cls = Class.forName("Library2", true, c);
Library2 lib2 = (Library2) cls.newInstance();
// If it worked, this should print "This is version 2."
// However, it still prints that it's version 1. Why?
lib2.bar();
}
public static void verifyValidPath(URL url) throws FileNotFoundException {
File filePath = new File(url.getFile());
if (!filePath.exists()) {
throw new FileNotFoundException(filePath.getPath());
}
}
}
Cuando ejecuto esto, lib1.foo()
causas "Esta es la versión 1." para ser impreso. Dado que esa es la versión de Library3 que está en el classpath cuando se inicia la aplicación, se espera esto.
Sin embargo, esperaba lib2.bar()
para imprimir "Esta es la versión 2.", lo que refleja que la nueva versión de Library3 se cargó, pero todavía imprime "Esta es la versión 1."
¿Por qué es que usar el nuevo cargador de clases con la versión de jar correcta cargada todavía da como resultado la versión anterior de jar que se utiliza? ¿Estoy haciendo algo mal? ¿O no estoy entendiendo el concepto detrás de los cargadores de clases? ¿Cómo puedo cambiar las versiones de jar de Library3 correctamente en tiempo de ejecución?
Agradeceria cualquier ayuda sobre este problema.
posible duplicado de http://stackoverflow.com/questions/6105124/java-classpath-classloading-multiple-versions-of-the-same-jar-project – abalogh
que inventó el término * posible * duplicar en SO? ¿Qué significa eso? – irreputable
@svkk FYI JDK8 tendrá [Project Jigsaw] (http://openjdk.java.net/projects/jigsaw/doc/draft-java-module-system-requirements-12) con la intención de resolver el problema Jar-hell . – Bringer128