在分离的 Java 平台模块中使用不同版本的依赖项

Using different versions of dependencies in separated Java platform modules

我预计可以在 myModuleA 中使用 Guava-19,在 myModuleB 中使用 guava-20,因为拼图模块有自己的 class路径。

假设 myModuleA 使用 Iterators.emptyIterator(); - 在 guava-20 中删除,myModuleB 使用新的静态方法 FluentIterable.of(); - 这在 guava-19 中不可用。不幸的是,我的测试是阴性的。在编译时,它看起来不错。与运行时相比,结果是 NoSuchMethodError。这意味着 class 是 classloader 上的第一个,它决定了哪个失败。

与底层耦合的封装?我给自己找了个理由。它不能被支持,因为传递依赖会和以前一样有同样的问题。如果一个guavaclass在ModuleA和ModuleB依赖的签名中发生了版本冲突。应该使用哪个class?

但为什么我们可以在整个互联网上阅读 "jigsaw - the module system stops the classpath hell"?我们现在有多个较小的 "similar-to-classpaths" 有同样的问题。与其说是问题,不如说是不确定性。

版本冲突

首先更正:你说模块有自己的class路径,这是不正确的。应用程序的 class 路径保持原样。与之并行的是引入了模块路径,但它基本上以相同的方式工作。特别是,所有应用程序 class 都由相同的 class 加载程序加载(至少默认情况下)。

所有应用程序只有一个 class 加载程序 classes 也解释了为什么不能有相同 class 的两个版本:整个 class 加载基础设施是建立在完全限定的 class 名称足以识别 class 和 class 加载程序的假设之上的。

这也打开了多版本解决方案的路径。就像之前一样,您可以通过使用不同的 class 加载器来实现。模块系统的本机方法是创建额外的 layers(每一层都有自己的加载器)。

模块地狱?

那么模块系统是否将class路径地狱替换为module hell?好吧,如果不创建新的 class 加载器,同一个库的多个版本仍然是不可能的,所以这个基本问题仍然存在。

另一方面,由于 split packages,现在您至少会在编译或启动时遇到错误。这可以防止程序出现微妙的错误行为,这也不是那么糟糕。

理论上,可以在您的应用程序中使用不同版本的 相同 库。实现这一点的概念:layering!

当您学习 Jigsaw under the hood 时,您会发现整个部分都专门讨论这个主题。

这个想法基本上是您可以使用这些层进一步 group 模块。层是在运行时构建的;他们有自己的 classloader。意思是:绝对有可能在一个应用程序中使用不同版本的模块——它们只需要进入 不同的 层。如图所示 - 这种 "multiple version support" 被 java/jigsaw 的工作人员积极讨论。这不是一个晦涩的功能 - 意味着 在一个引擎盖下支持不同的模块版本。

此时唯一的免责声明:不幸的是,那里没有 "complete" 源代码示例(我知道),因此我只能 link 那个 Oracle 演示文稿。

换句话说:即将出现一些解决此版本控制问题的方法 - 但需要更多时间才能使用此新代码在现实世界的代码中体验主意。准确地说:您可以有不同的层,这些层被不同的 class 加载程序 隔离 。有 no 支持允许您 "the same object" 同时使用 modV1 和 modV2。您只能有 两个 个对象,一个使用 modV1,另一个使用 modV2。

(德国读者可能想看看 here - 该出版物包含对图层主题的另一介绍)。

Java 9 没有解决此类问题。简而言之,java 9 中所做的是将 classic 访问修饰符(public、受保护、package-private、私有)扩展到 jar 级别。

在 java9 之前,如果模块 A 依赖于模块 B,那么来自 B 的所有 public classes 对 A 都是可见的。

使用 Java9,可以配置可见性,因此它可以仅限于 classes 的一个子集,每个模块可以定义哪些包导出和哪些包需要。

大部分检查都是由编译器完成的。

从运行次perspective(classloader architecture)开始,没有大的变化,所有的应用程序模块都是由同一个class加载器加载的,所以不可能有相同的class 在同一个 jvm 中使用不同的版本,除非你使用像 OSGI 这样的模块化框架或自己操作 classloader。

正如其他人所暗示的那样,JPMS 层可以提供帮助。您可以手动使用它们,但 Layrry 可能对您有所帮助,它是 运行 分层应用程序的流畅 API 和基于配置的启动器。它允许您通过配置定义层结构,它会为您启动层图。它还支持运行时层的动态addition/removal。

免责声明:我是 Layrry 的最初创建者