java - jar hell : how to use a classloader to replace one jar library version with another at runtime

标签 java jar classpath classloader

我对 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.”,这反射(reflect)了新版本的 Library3 已加载,但它仍然打印“This is version 1. "

为什么在加载了正确的 jar 版本的情况下使用新的类加载器仍然会导致使用旧的 jar 版本?难道我做错了什么?还是我不理解类加载器背后的概念?如何在运行时正确切换 Library3 的 jar 版本?

我将不胜感激有关此问题的任何帮助。

最佳答案

我不敢相信超过 4 年没有人正确回答这个问题。

https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

Sergei,您的示例的问题是库 1,2 和 3 位于默认类路径上,因此作为 URLClassloder 的父级的应用程序类加载器能够从库 1,2 和 3 加载类。

如果您从类路径中删除这些库,应用程序类加载器将无法从中解析类,因此它将解析委托(delegate)给它的子类 - URLClassLoader。所以这就是你需要做的。

关于java - jar hell : how to use a classloader to replace one jar library version with another at runtime,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6909306/

相关文章:

java - 如何使用JAVA从字符串中获取一个子字符串的特定值?

java - java中的@QueryParam或HttpServletRequest

java - 如何从使用在 "Classpath"中添加的 *.jar 文件的 eclipse java 项目创建 jar 文件

java - Eclipse 可执行 Jar 资源文件

java - 我的 Java 项目的 gradle 构建出错

scala - 运行SBT runTask时如何访问资源?

java - SQL异常 : no such table

Java : Mac command key for multiple selection in a list (instead of control)

java - JNLP找不到使用Maven构建的主类

Maven 类路径错误多个 SLF4J 绑定(bind)