Jar hell:如何使用类加载器在运行时用另一个jar库版本替换另一个版本
我对Java还比较陌生,所以请耐心等待。
我的问题是我的Java应用程序依赖于两个库。让我们称它们为库 1 和库 2。这两个库都相互依赖库 3。然而:
- 库 1 正好需要库 3 的版本 1。
- 库 2 正好需要库 3 的版本 2。
这正是JAR地狱的定义(或者至少是它的一个变体)。如链接中所述,我无法在同一类加载器中加载第三个库的两个版本。因此,我一直在试图弄清楚我是否可以在应用程序中创建一个新的类加载器来解决这个问题。我一直在研究URLClassLoader,但我无法弄清楚。
下面是演示问题的示例应用程序结构。应用程序的 Main 类 (Main.java) 尝试实例化 Library1 和 Library2,并运行这些库中定义的一些方法:
Main.java(原始版本,在任何解决方案尝试之前):
public class Main {
public static void main(String[] args) {
Library1 lib1 = new Library1();
lib1.foo();
Library2 lib2 = new Library2();
lib2.bar();
}
}
库 1 和库 2 都共享对库 3 的相互依赖关系,但库 1 正好需要版本 1,而库 2 恰好需要版本 2。在此示例中,这两个库都只打印它们看到的 Library3 版本:
库1.java:
public class Library1 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 1."
}
}
库2.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.
}
}
当然,还有多个版本的Library3。他们所做的就是打印他们的版本号:
库 3 的版本 1(库 1 要求):
public class Library3 {
public void printVersion() {
System.out.println("This is version 1.");
}
}
库 3 的版本 2(库 2 要求):
public class Library3 {
public void printVersion() {
System.out.println("This is version 2.");
}
}
当我启动应用程序时,类路径包含 Library1 (lib1.jar)、Library2 (lib2.jar) 和 Library 3 的版本 1 (lib3-v1/lib3.jar)。这对库 1 来说很好,但对库 2 不起作用。
我需要做的是在实例化 Library2 之前替换出现在类路径上的 Library3 版本。我的印象是URLClassLoader可以用于此目的,所以这是我尝试过的:
Main.java(新版本,包括我对解决方案的尝试):
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());
}
}
}
当我运行此命令时,会导致打印“这是版本 1”。由于这是应用程序启动时类路径上的 Library3 版本,因此这是预料之中的。lib1.foo()
但是,我本来以为打印“这是版本 2”,反映出 Library3 的新版本已加载,但它仍然打印“这是版本 1”。lib2.bar()
为什么使用加载了正确jar版本的新类加载器仍然会导致使用旧的jar版本?我做错了什么吗?还是我不理解类加载器背后的概念?如何在运行时正确切换库 3 的 jar 版本?
我将不胜感激有关此问题的任何帮助。