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


117

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

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中吗?

您如何处理这个问题?

是否有某种技巧可以以某种方式将罐子“合并”到“ required.jar”中,从而使罐子被Classloader或以某种方式链接为“一个单元/包装” ?

Answers:


56

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

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

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


13
引导程序类加载器委托给适当的。实例化新类时,将调用更具体的类加载器。如果找不到对您尝试加载的类的引用,则会将其委派给其父级。 请耐心等待,但这取决于默认情况下“父级优先”的类加载器策略。换句话说,子类将首先要求其父类加载该类,并且仅在整个层次结构无法加载该类时才加载,否?
deckingraj

5
否-通常,类加载器在查找类本身之前将其委托给其父级。请参阅类加载器的javadoc类。
Joe Kearney 2014年

1
我认为tomcat是以此处描述的方式执行此操作的,但是“常规”授权是首先询问家长
rogerdpack 2014年

@deckingraj:经过一番谷歌搜索后,我从oracle docs中发现了这一点:“在委托设计中,类加载器尝试自身加载类之前将类加载委托给其父类。[...]如果父类加载器无法加载类,类加载器尝试加载类本身。实际上,类加载器仅负责加载父级不可用的类。我将进一步调查。如果这将作为默认实现出现,我将相应地更新响应。(docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html
卢卡Putzu


6

Classloader将首先从恰好位于类路径中的jar中加载类。通常,不兼容的库版本在程序包中会有所不同,但是在极少数情况下,它们确实不兼容并且不能用一个替代-尝试jarjar。


6

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

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

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


1

您可以使用URLClassLoaderfor 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();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.