Java,Classspath,Classload = > 同一 jar/project 的多个版本

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

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

类装入器是否足够聪明以某种方式将它们分离?最有可能不是?如果所有三个 jar 中的 Class 是相同的,Classloader 将如何处理这个问题。哪个上膛了,为什么?

Classloader 是只选择一个 jar 还是任意混合类?例如,如果一个类是从 Version-1.jar 加载的,那么从同一个类加载器加载的所有其他类都将进入同一个 jar?

你如何处理这个问题?

是否有一些技巧,以某种方式“合并”罐到“必需的. jar”,使这些被视为“一个单元/包”的 Classloader,或以某种方式链接?

95012 次浏览

Classloader 将首先从恰好位于类路径中的 jar 加载类。 通常情况下,不兼容的库版本在包中会有所不同, 但在不太可能的情况下,它们实际上是不兼容的,而且不能用一次性的 jarjar 代替。

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

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

解决此类错误的推荐做法是,对具有冲突依赖关系的每组库使用单独的类加载器。这样,如果类加载器试图从库中加载类,那么相关类将由同一个类加载器加载,而这个类加载器不能访问其他库和依赖项。

每个类负载只挑选一个类,通常是找到的第一个类。

OSGi 旨在解决同一个 jar 的多个版本的问题。春分阿帕奇・费里斯是 OSGi 常用的开源实现。

与类加载器相关的问题是一个相当复杂的问题。 无论如何,你应该记住一些事实:

  • 应用程序中的类加载器通常不止一个。引导类加载程序将委托给适当的。实例化新类时,将调用更具体的类加载器。如果它没有找到要加载的类的引用,它就会委托给它的父类,依此类推,直到找到引导类加载器。如果它们都没有找到要加载的类的引用,则会出现 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);

我总是发现类加载器自定义是一个棘手的任务。我宁愿建议避免多个 如果可能的话,不兼容的依赖关系。

您可以使用 URLClassLoader来满足从 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();