在 Java 中访问包的多个版本

Posted

技术标签:

【中文标题】在 Java 中访问包的多个版本【英文标题】:Access multiple versions of a package in Java 【发布时间】:2011-05-25 09:16:52 【问题描述】:

我们有几个使用 Apache HTTPClient 3 发出 HTTP 请求的应用程序。最近,出于各种原因,我们也开始创建使用 HTTPClient 4 的 Web 服务客户端。 Apache 的立场是“主要版本不向后兼容”。虽然我很想更新我们所有的项目以使用版本 4,但这根本不可行。

所以,虽然我的主要问题相当笼统,但我的具体问题是。 如何在同一个应用程序中使用 HTTPClient 版本 3 和 4? 在我们的例子中,应用程序可以是 Web、桌面或命令行应用程序。

我已经阅读了SO question for java-dynamically-load-multiple-versions-of-same-class,它看起来很接近,但我不太关心动态部分。事实上,我希望 JAR 与应用程序一起提供(例如,用于 Web 应用程序的 WEB-INF/lib)我还看到 OSGi 在与此类似的问题中提到了很多,但它似乎有点矫枉过正或过于复杂(也许一个简单的例子可以证明并非如此)。

最后,我希望能够为团队提供一组他们可以放入的 jars,并且它只是使用 HTTP Client 3 独立于他们的项目工作。

【问题讨论】:

在这种情况下,架构治理本来是一个好主意:决定要么坚持使用第 3 版,要么将所有内容迁移到第 4 版。现在您必须为拼凑解决方案支付实施成本,而且随着时间的推移会变得更糟。 @Anon:没有冒犯,但在不了解情况的情况下,我不建议做出这样的笼统声明。我说“出于各种原因”是为了避开这样的 cmets。 OSGi 至少可以工作(你需要确定它是否矫枉过正),我不知道有什么简单的方法可以做到这一点,除了有两个 Web 服务器,每个版本一个图书馆。 【参考方案1】:

正如其他人所说,您可以创建多个类加载器并单独加载两个版本。这部分很简单。

问题是,这实质上分割了您的“类空间”,并且仍然很难从应用程序的某些部分引用 v3,同时从应用程序的其他部分引用 v4。您必须非常仔细地划分您的应用程序...那么为什么不将其拆分并交付两个应用程序呢?

如果您能够将功能分解到服务中,OSGi 可能是一个解决方案。但是将遗留应用程序转换为 OSGi 并不是一件轻而易举的事情,而且它肯定不会是从您所陷入的陷阱中廉价逃脱的机会。我是作为一本关于 OSGi 的书的作者和一位著名的 OSGi 布道者这样说的。将您的应用程序转换为 OSGi 的长期目标为您带来巨大的好处,但也会涉及大量的前期成本。

【讨论】:

【参考方案2】:

一个简单但直接的解决方案是获取 HttpClient3 和 HttpClient4 的源代码,并将包名称重构为类似

org.apache.commons.httpclient3 用于 HttpClient3 和 org.apache.commons.httpclient4 用于 HttpClient4 以避免冲突。然后编译,打包,完成。

现在可以很容易地在两个实现之间切换,并且它们不会在类加载器中发生冲突。

【讨论】:

虽然这适用于一个罐子,但它产生的问题多于解决的问题。例如,您可能需要 apache.commons.logging,这两个版本都将依赖于不同版本的公共日志记录(因为它们是在不同时间发布的)。现在您可以按照上述方式重新打包 commons 日志记录,但是您必须返回并手动编辑重新打包的 http 客户端中的所有源代码才能正确引用。很快这样的解决方案就会变得一团糟。 它们真的依赖于不同版本的公共日志记录吗?最新的应该两者都可以正常工作,不是吗? 请注意,commons-lang 人正在走这条路。 Commons Lang 3 将使用包名 org.apache.commons.lang3。如果您要进行不兼容的 API 更改,那么新的包名称可能不是最糟糕的主意。 Edwin,我不认为仅凭猜测就可以说“这造成的问题多于解决的问题”。如果您使用最新版本的公共日志记录,我认为不会有问题。我使用了许多依赖于不同 jcl 版本的 apache 项目,并且使用最新的 jcl 永远不会导致问题。但是,您可以使用 slf4j 及其 jcl-over-slf4j.jar 来完全删除 jcl 并通过 slf4j 桥接所有内容以消除 jcl 版本冲突。无需 rafactor jcl。 如果产品使用最新版本,那不是问题。当处理依赖项时,问题就出现了,其中双重打包项目的“旧”版本不适用于下游依赖项的“较新”版本。 “这种事经常发生吗?”并不是真正的问题,好像一旦它使包装方案处于危险之中就会发生这种情况。这个建议不是一个坏的做事方式,但它有它的风险;这就是我想要指出的。【参考方案3】:

您必须为 v3 和 v4 使用单独的类加载器。将 v3 和 v4 jar 放在应用程序类路径之外的单独文件夹中。使用 URLClassLoadedr 加载每个版本。您传递给每个类加载器的 URL 应包含特定版本的 HTTP 客户端的 URL。

但是我可以给你一个建议吗?在开始之前,首先检查您是否真的需要所有这些。版本可能不兼容是对的。但他们很有可能是。

【讨论】:

我认为你的意思是“他们很可能不是”我知道 HTTP 客户端 v3 和 V4 不能很好地配合使用。【参考方案4】:

使用多个类加载器,一个用于您希望使用的每个 HTTP 客户端。

最简单的方法是扩展 URLClassLoader 并破解它以分别硬编码每个版本的类路径。然后,您只需要确保其余代码知道要使用哪个版本的 HTTP 客户端(并访问正确的类加载器以获取它)。

【讨论】:

这对于 JAR 必须与 WAR 一起提供的 web 应用程序如何工作? 您发布了引用的每个库的所有版本,但将它们放在 web-inf/lib 以外的位置。您的自定义类加载器在“其他地方”位置查找。这可以防止战争容器直接引用它们。

以上是关于在 Java 中访问包的多个版本的主要内容,如果未能解决你的问题,请参考以下文章

Java学习笔记--不同包的访问权限

java中使用URLClassLoader访问外部jar包的java类

java中不同包的受保护成员访问-好奇心[重复]

Java 包及访问控制权限

Java:当我使用extends关键字实现多重继承时,不同包的不同类中未访问受保护的方法

2020/7/8 JAVA总结之:匿名对象/内部类/包的声明与访问/访问修饰符/代码块