Java,Classpath,Classloading =>同一个jar /项目的多个版本

时间:2011-05-24 02:28:42

标签: java jar classpath classloader

我知道对于有经验的程序员来说这可能是一个愚蠢的问题。但我有一个库(一个http客户端),我的项目中使用的一些其他框架/ jar需要。但所有这些都需要不同的主要版本,如:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

类加载器是否足够智能以某种方式分离它们?很可能不是吗?如果Class在所有三个jar中都相同,Classloader如何处理这个问题。加载哪一个,为什么?

Classloader是否只拾取一个jar或者是否任意混合类?因此,例如,如果从Version-1.jar加载一个类,那么从同一个类加载器加载的所有其他类都将进入同一个jar?

你如何处理这个问题?

是否有一些技巧以某种方式“将”罐子“合并”到“required.jar”中,以便Classloader将其视为“一个单元/包”,或以某种方式链接?

5 个答案:

答案 0 :(得分:51)

类加载器相关问题是一个非常复杂的问题。 在任何情况下,你都应该记住一些事实:

  • 应用程序中的类加载器通常不止一个。引导类加载器委托给适当的。实例化新类时,将调用更具体的类加载器。如果它没有找到您正在尝试加载的类的引用,它会委托给它的父类,依此类推,直到您进入引导类加载器。如果他们都没有找到您尝试加载的类的引用,则会得到ClassNotFoundException。

  • 如果您有两个具有相同二进制名称的类,可以通过相同的类加载器进行搜索,并且您想知道要加载哪个类,则只能检查特定类加载器尝试解析类的方式名称

  • 根据java语言规范,类二进制名称没有唯一性约束,但据我所知,每个类加载器应该是唯一的。

我可以想出一种方法来加载具有相同二进制名称的两个类,并且它涉及通过两个不同的类加载器来覆盖默认行为来加载它们(及其所有依赖项)。 一个粗略的例子:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

我总是发现类加载器定制是一项棘手的任务。我宁愿建议避免多重 如果可能,不兼容的依赖项。

答案 1 :(得分:20)

每个classload只选择一个类。通常是第一个找到的。

OSGi旨在解决同一个jar的多个版本的问题。 EquinoxApache Felix是OSGi的常见开源实现。

答案 2 :(得分:6)

Classloader将首先从恰好位于类路径中的jar中加载类。 通常,不兼容的库版本会有不同的包, 但在不太可能的情况下,它们真的不兼容,不能用一个替换 - 尝试jarjar。

答案 3 :(得分:6)

类加载器按需加载类。这意味着应用程序和相关库首先需要的类将在其他类之前加载;加载依赖类的请求通常在依赖类的加载和链接过程中发出。

您可能会遇到LinkageError声明已经遇到过类加载器的重复类定义,通常不会尝试确定应该首先加载哪个类(如果有两个或更多同名的类存在于加载器的类路径)。有时,类加载器将加载类路径中出现的第一个类并忽略重复的类,但这取决于加载器的实现。

解决此类错误的建议做法是为每组具有冲突依赖关系的库使用单独的类加载器。这样,如果类加载器尝试从库加载类,则依赖类将由无法访问其他库和依赖项的同一类加载器加载。

答案 4 :(得分:0)

您可以使用URLClassLoader作为require从diff-2版本的jar中加载类:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();