【问题标题】:Java - how to load different versions of the same class?Java - 如何加载同一类的不同版本?
【发布时间】:2012-07-30 08:36:59
【问题描述】:

我已经阅读了很多关于 Java 类加载器的内容,但到目前为止,我还没有为这个简单的问题找到答案:

我在 v1.jarv2.jar 罐子中有两个版本的 com.abc.Hello.class。我想在我的应用程序中使用两者。最简单的方法是什么?

我没想到会这么简单,但类似这样的东西会很棒:

Classloader myClassLoader = [magic that includes v1.jar and ignores v2.jar]
Hello hello = myclassLoader.load[com.abc.Hello]

在不同的班级:

Classloader myClassLoader = [magic that includes v2.jar and ignores v1.jar]
Hello hello = myclassLoader.load[com.abc.Hello]

我想避免使用 OSGi。

【问题讨论】:

  • 就我个人而言,我不认为我会想要这样的魔法,因为它会导致维护/调试的噩梦......你能详细说明你为什么需要这个吗?
  • 多个插件依赖于同一共享库的不同版本将是造成这种情况的原因。
  • @beny23 你没听说过 jar hell 吗?我刚刚又打了。 selenium 正在使用一个版本,而 google 组件使用的是不同版本的番石榴,而 selenium 失败了,所以我需要 selenium 使用它测试过的 jar,而 google 组件使用它测试过的不同版本。

标签: java class jvm classloader


【解决方案1】:

对于@helios checkout github.com/atulsm/ElasticsearchClassLoader接受的答案的示例实现

【讨论】:

  • 这个例子似乎有一些问题:1:它抛出异常:“org.elasticsearch.client.transport.NoNodeAvailableException:没有配置的节点可用”,2:有一个“CustomClassLoader” is 似乎根本没有使用。一个很好的例子应该是一个没有无用的额外误导信息的工作案例......
【解决方案2】:

你走对了。您必须考虑一些事情。

正常情况是使用存在于父类加载器中的类。因此,如果您想要两个版本,则这些类不能存在。

但如果你想进行交互,你可以使用反射,甚至更好的通用接口。所以我会这样做:

common.jar:
BaseInterface

v1.jar:
SomeImplementation implements BaseInterface

v2.jar:
OtherImplementation implements BaseInterface

command-line:
java -classpath common.jar YourMainClass
// you don't put v1 nor v2 into the parent classloader classpath

Then in your program:

loader1 = new URLClassLoader(new URL[] {new File("v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
loader2 = new URLClassLoader(new URL[] {new File("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();

【讨论】:

  • 之所以赞成是因为关于在主类路径中没有两个版本的解释。如果您还可以解释他们如何在代码中使用 c1 和 c2 以及如何编译,那将会很有帮助。也就是他们如何调用c1.someMethod()
  • 谢谢!更多细节:“通用”类可以通过主代码和 v1/v2 代码传递。公共类是由父类加载器加载的类,因此它们对它们中的任何一个都可用(父类、基于 v1 和基于 v2)。所有 JRE 类都属于这一类。这就是为什么您可以通过示例传递 java.lang.String 的原因。
  • @CPerkins:好吧,也许我已经隐含了。无论如何:您总是使用 BaseInterface,它存在于父类加载器中,因此,可从主代码(如 String 或任何其他 JRE 类)中获得。您永远不会使用仅在子类加载器中加载的主代码类。底线:BaseInterface 必须具有您从主代码中需要的方法。
  • 我发现上面的代码总是加载类路径中的第一个类,尽管要求它从两个不同的 jar 加载。其原因可能是类加载器父级延迟。通过将 parent 设置为 null,我能够从两个不同的 jar 加载相同的类: loader1 = new URLClassLoader(new URL[] {new File("v1.jar").toURL()}, null); loader2 = new URLClassLoader(new URL[] {new File("v2.jar").toURL()}, null);
  • @helios 我已经实施了您的提议,以处理弹性搜索客户端使用的非向后兼容版本。结帐github.com/atulsm/ElasticsearchClassLoader
【解决方案3】:

这取决于你打算如何处理这两个版本以及如何处理,但通常你至少可以在不同的类加载器中加载 2 个版本的类,然后设置 Thread.contextClassloader()然后玩……

http://www.javaworld.com/javaqa/2003-06/01-qa-0606-load.htmlhttp://docs.oracle.com/javase/jndi/tutorial/beyond/misc/classloader.html

【讨论】:

    【解决方案4】:

    您几乎已经编写了解决方案。 我希望下面的代码片段会有所帮助。

    ClassLoader cl = new URLClassLoader(new URL[] {new File("v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
    Class<?> clazz = cl.loadClass("Hello");
    

    v1.jar 替换为v2.jar,此代码将加载版本#2。

    【讨论】:

    • +1。我已将您的新 URLClassLoader 行添加到我的帖子中 :) 这就是我所缺少的...
    猜你喜欢
    • 1970-01-01
    • 2018-03-14
    • 2010-12-14
    • 2011-08-13
    • 1970-01-01
    • 2010-09-08
    • 2017-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多