Jar hell:如何在运行时使用类加载器将一个jar库版本替换为另一个jar库版本 [英] Jar hell: how to use a classloader to replace one jar library version with another at runtime

查看:163
本文介绍了Jar hell:如何在运行时使用类加载器将一个jar库版本替换为另一个jar库版本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我还是比较新的Java,所以请耐心等待。

I'm still relatively new to Java, so please bear with me.

我的问题是我的Java应用程序依赖于两个库。让我们称它们为库1和库2.这两个库共享对库3的相互依赖。但是:

My issue is that my Java application depends on two libraries. Let's call them Library 1 and Library 2. Both of these libraries share a mutual dependency on Library 3. However:


  • 库1需要完全版本图书馆3中的1个。

  • 图书馆2完全要求图书馆3的第2版。

这正是 JAR地狱(或至少其中一个变体)的定义。
如链接中所述,我无法在同一个类加载器中加载第三个库的两个版本。因此,我一直试图弄清楚是否可以在应用程序中创建一个新的类加载器来解决这个问题。我一直在研究 URLClassLoader ,但是我无法弄明白。

This is exactly the definition of JAR hell (or at least one its variations). As stated in the link, I can't load both versions of the third library in the same classloader. Thus, I've been trying to figure out if I could create a new classloader within the application to solve this problem. I've been looking into URLClassLoader, but I've not been able to figure it out.

这是一个演示问题的示例应用程序结构。应用程序的Main类(Main.java)尝试实例化Library1和Library2并运行在这些库中定义的一些方法:

Here's an example application structure that demonstrates the problem. The Main class (Main.java) of the application tries to instantiate both Library1 and Library2 and run some method defined in those libraries:

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 and Library2 both share a mutual dependency on Library3, but Library1 requires exactly version 1, and Library2 requires exactly version 2. In the example, both of these libraries just print the version of Library3 that they see:

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。他们所做的只是打印他们的版本号:

And then, of course, there are multiple versions of Library3. All they do is print their version numbers:

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不起作用。

When I launch the application, the classpath contains Library1 (lib1.jar), Library2 (lib2.jar), and version 1 of Library 3 (lib3-v1/lib3.jar). This works out fine for Library1, but it won't work for Library2.

我需要做的是在实例化之前替换类路径上出现的Library3的版本Library2。我的印象是 URLClassLoader 可能是用于此,所以这是我尝试的:

What I somehow need to do is replace the version of Library3 that appears on the classpath before instantiating Library2. I was under the impression that URLClassLoader could be used for this, so here is what I tried:

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的版本,所以这是预期的。

When I run this, lib1.foo() causes "This is version 1." to be printed. Since that's the version of Library3 that's on the classpath when the application starts, this is expected.

但是,我期待 lib2.bar() 打印这是版本2,反映新版本的Library3已加载,但仍然打印这是版本1。

However, I was expecting lib2.bar() to print "This is version 2.", reflecting that the new version of Library3 got loaded, but it still prints "This is version 1."

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

Why is it that using the new classloader with the right jar version loaded still results in the old jar version being used? Am I doing something wrong? Or am I not understanding the concept behind classloaders? How can I switch jar versions of Library3 correctly at runtime?

我很感激这个问题的任何帮助。

I would appreciate any help on this problem.

推荐答案

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

I can't believe that for more than 4 years no one has answered this question correctly.

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


ClassLoader类使用委托模型来搜索类
和资源。 ClassLoader的每个实例都有一个关联的父
类加载器。当请求查找类或资源时,
ClassLoader实例会在尝试查找
类或资源本身之前,将对类或
资源的搜索委托给其父类加载器。虚拟机的内置类加载器
称为bootstrap类加载器,它本身不具有父级,但
可以作为ClassLoader实例的父级。

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.

谢尔盖,你的例子的问题是图书馆1,2和3是在默认的类路径上,因此作为URLClassloder的父级的Application类加载器能够从库1,2和1中加载类。 3。

Sergei, the problem with your example was that Library 1,2 & 3 were on the default class path, so the Application classloader which was the parent of your URLClassloder was able to load the classes from Library 1,2 & 3.

如果从类路径中删除库,则Application类加载器将无法从中解析类,因此它会将重新解析委托给其子类 - URLClassLoader。这就是你需要做的事情。

If youremove the libraries from the classpath, the Application classloader won't be able to resolve classes from them so it will delegate resolvation to its child - the URLClassLoader. So that is what you need to do.

这篇关于Jar hell:如何在运行时使用类加载器将一个jar库版本替换为另一个jar库版本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆