【发布时间】:2011-10-18 01:37:48
【问题描述】:
我对 Java 还是比较陌生,所以请多多包涵。
我的问题是我的 Java 应用程序依赖于两个库。我们称它们为库 1 和库 2。这两个库都对库 3 具有相互依赖关系。但是:
- 库 1 需要库 3 的版本 1。
- 库 2 需要库 3 的版本 2。
这正是JAR hell 的定义(或至少一个它的变体)。 如链接中所述,我无法在同一个类加载器中加载第三个库的两个版本。因此,我一直试图弄清楚是否可以在应用程序中创建一个新的类加载器来解决这个问题。我一直在研究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();
}
}
Library1 和 Library2 都对 Library3 具有相互依赖关系,但 Library1 需要的正是版本 1,而 Library2 需要的正是版本 2。在示例中,这两个库都只打印他们看到的 Library3 的版本:
Library1.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.
}
}
然后,当然,Library3 有多个版本。他们所做的只是打印他们的版本号:
Library3 的版本 1(Library1 需要):
public class Library3 {
public void printVersion() {
System.out.println("This is version 1.");
}
}
Library3 的第 2 版(Library2 需要):
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)。这适用于 Library1,但不适用于 Library2。
我需要做的是在实例化 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());
}
}
}
当我运行它时,lib1.foo() 会导致“这是版本 1”。要打印。由于这是应用程序启动时类路径上的 Library3 版本,因此这是预期的。
但是,我期待 lib2.bar() 打印“This is version 2.”,这反映了新版本的 Library3 已加载,但它仍然打印“This is version 1”。
为什么在加载了正确的 jar 版本的情况下使用新的类加载器仍然会导致使用旧的 jar 版本?难道我做错了什么?还是我不理解类加载器背后的概念?如何在运行时正确切换 Library3 的 jar 版本?
我将不胜感激有关此问题的任何帮助。
【问题讨论】:
-
谁发明了可能这个术语在 SO 上重复?这是什么意思?
-
@svkk 仅供参考 JDK8 将有 Project Jigsaw 用于解决 Jar-hell 问题。
-
更好的解决方案可能是使用 code.google.com/p/jarjar 之类的东西来重新打包库及其依赖项。
-
@irreputable 的人,一想到阅读问题墙就发抖
标签: java jar classpath classloader