为什么需要项目Jigsaw / JPMS?

82

对我来说,Java的包管理系统一直都很简单和有效。JDK本身就大量使用它,我们一直在使用它来模拟命名空间和模块的概念。

Project Jigsaw(又称Java平台模块系统)试图填补什么空缺呢?

官方网站上是这样描述的:

该项目的目标是为Java SE平台设计和实现一个标准的模块系统,并将该系统应用于平台本身和JDK。

5个回答

104

Jigsaw和OSGi试图解决同样的问题:如何允许更粗粒度的模块进行交互,同时保护它们的内部。

在Jigsaw的情况下,更粗粒度的模块包括Java类、包及其依赖项。

这里有一个例子:Spring和Hibernate。两者都依赖于第三方JAR CGLIB,但它们使用不兼容的版本。如果您依赖于标准JDK,该怎么办?包含Spring想要的版本会破坏Hibernate,反之亦然。

但是,如果您有像Jigsaw这样的更高级别模型,就可以轻松管理不同模块中不同版本的JAR。将它们视为更高级别的包。

如果您从GitHub源代码构建Spring,也会看到这一点。他们重新设计了框架,使其由多个模块组成:核心、持久化等。您可以选择应用程序需要的最小模块依赖项并忽略其余部分。以前是一个单独的Spring JAR,其中包含所有.class文件。

更新:五年后 - Jigsaw可能仍需要解决一些问题


5
如果你需要使用完全相同的模块,但是两个不同的版本,该怎么办?他们应该添加一些支持以便相同类的两个版本可以共存吗? - Didier A.
7
这篇文章存在误导,因为实际上 Java 9 中即将发布的内容与其描述不符。或许在撰写时它是准确的。 - xenoterracide
1
项目已完成并发布于Java 9:https://mreinhold.org/blog/jigsaw-complete - Zasz
@xenoterracide,你不能因为某人不是千里眼而责怪他。这篇文章比Java 9早五年。你也要检查Jon Skeet的每个答案吗? - duffymo
这篇文章不太适用了。Java模块有意地没有解决版本控制的问题,请看这个线程。目前还没有简单的方法来避免我们老朋友NoSuchMethodErrorNoClassDefFoundError - Tamas Hegedus

47

AFAIK计划是使JRE更具模块化,即拥有可选的较小的jar包或者您只需下载/升级所需的功能。

这旨在减少膨胀并为您提供放弃传统模块的选项,这些模块可能大多数人都不使用。


7
被采纳的答案是正确的,但这个答案更好,因为它解释了实际期望的效果。+1,非常值得。 - Silviu Burcea
我很好奇,这是否意味着如果我有Google Guava作为依赖项,但我只使用其中的ImmutableList,那么我只能导入ImmutableList依赖项,而将Guava的其余类排除在外? - tmn
1
@ThomasN。当你使用 import 时,它所做的只是将该类引入编译器的命名空间中。如果你实际上没有使用它,它不会出现在生成的字节码中。 如果你实际上使用了该类,则需要在运行时拥有该类和它使用的每个类。理论上,你可以创建一个裁剪版的Guava API版本,其中仅包含你需要的内容,并使用该JAR文件代替。但实际上这种做法容易出错,在大多数情况下没有什么用处,最终你还是得添加整个JAR文件才能发布。 - Peter Lawrey

45

根据Mark ReinholdDevoxx比利时的主题演讲,Project Jigsaw将解决两个主要问题:

  1. Classpath
  2. 庞大的单体JDK

Classpath有什么问题?

我们都知道JAR Hell。这个术语描述了类加载过程可能无法正常工作的各种方式。Classpath最为人所知的限制包括:

  • 很难确定是否存在冲突。像Maven这样的构建工具可以根据构件名称做得很好,但如果构件本身具有不同的名称但内容相同,则可能会发生冲突。
  • Jar文件的根本问题在于它们不是组件。它们只是一堆文件容器,将被线性搜索。类路径是一种查找类的方式,无论它们在哪个组件中,哪个包中或者它们的预期用途如何。

大而臃肿的JDK

JDK的巨大而臃肿的特性导致了几个问题:

  • 它不适合小型设备。即使小型物联网类型的设备具有运行SE类VM的处理器,但它们并不一定具有足够的内存来容纳整个JDK,尤其是当应用程序仅使用其中的一小部分时。
  • 这甚至成为云计算中的一个问题。云计算的核心是优化硬件的使用,如果你拥有数千个包含整个JDK的映像,但应用程序只使用其中的一小部分,那么这将是一种浪费。

模块:共同解决方案

为了解决上述问题,我们将模块作为Java程序组件的一种基本形式。一个模块是一个命名的、自我描述的代码和数据集合。它的代码被组织成一组包,包含类型,即Java类和接口;它的数据包括资源和其他静态信息。
为了控制其代码如何引用其他模块中的类型,一个模块声明了它需要哪些其他模块才能进行编译和运行。为了控制其他模块中的代码如何引用其包中的类型,一个模块声明了它导出哪些包。
模块系统定位所需的模块,并且与类路径机制不同,确保模块中的代码只能引用它依赖的模块中的类型。Java语言和Java虚拟机的访问控制机制防止代码访问未被其定义模块导出的包中的类型。
除了更可靠外,模块化还可以提高性能。当模块中的代码引用包中的类型时,该包保证要么在该模块中定义,要么在该模块读取的模块中精确地定义一次。因此,在查找特定类型的定义时,无需在多个模块中搜索,更不用说整个类路径了。
需要遵循的JEPs:

Jigsaw是一个持续了很多年的庞大项目。它有大量的JEPs,这些是获取关于该项目更多信息的好地方。以下是其中一些JEPs:

结束语

模块系统的现状报告的最初版本中,马克·莱恩霍尔德描述了模块系统的具体目标如下:

  • 可靠的配置,以取代易出错的类路径机制,使程序组件能够声明彼此之间的明确依赖关系,以及
  • 强大的封装,允许组件声明其公共类型对其他组件可访问和不可访问的范围。

这些功能将直接受益于应用程序开发人员、库开发人员和Java SE平台本身的实现者,也将间接地使平台具有可扩展性、更高的完整性和更好的性能。


3
Mark Reinhold是Oracle Java平台组的首席架构师,这个答案基本上是他对那个确切问题的直接回答。 - Jason
1
为了量化这一点,HelloWorld 可以使用 15 MB 而不是 553 MB;https://youtu.be/rFhhLXcOBsk?t=31m12s - user1133275

15
For the sake of argument, let's assume that Java 8 (and earlier) already has a "form" of modules (jars) and module system (the classpath). However, there are well-known problems with these.
By examining the problems, we can illustrate the motivation for Jigsaw. (The following assumes we are not using OSGi, JBoss Modules, etc, which certainly offer solutions.) Problem 1: public is too public Consider the following classes (assume both are public):
com.acme.foo.db.api.UserDao
com.acme.foo.db.impl.UserDaoImpl

在Foo.com,我们可能决定让团队使用而不是直接使用。然而,在类路径上没有强制执行的方式。
在Jigsaw中,模块包含一个文件,允许我们明确声明对其他模块公开的内容。也就是说,public有细微差别。例如:
// com.acme.foo.db.api.UserDao is accessible, but
// com.acme.foo.db.impl.UserDaoImpl is not 
module com.acme.foo.db {
    exports com.acme.foo.db.api;
}

问题2:反射是不受限制的

假设在Java 8中有以下类:

Class c = Class.forName("com.acme.foo.db.impl.UserDaoImpl");
Object obj = c.getConstructor().newInstance();

这就是说:反射功能强大且必要,但如果不加限制,它可能会以不良方式深入模块的内部。Mark Reinhold提供了一个相当令人震惊的例子。(该SO帖子在这里。)
在Jigsaw中,强封装提供了拒绝访问类(包括反射)的能力。(这可能取决于命令行设置,待 JDK 9 的修订技术规范。)请注意,由于 Jigsaw 用于 JDK 本身,Oracle 声称这将使 Java 团队更快地创新平台内部。 问题3:类路径擦除了架构关系 一个团队通常对于jar之间的关系有一种心理模型。例如,foo-app.jar 可能使用 foo-services.jar,后者又使用了 foo-db.jar。我们可能会断言,在 foo-app.jar 中的类不应该绕过“服务层”并直接使用 foo-db.jar。然而,没有办法通过类路径来执行这个断言。Mark Reinhold提到了this here
相比之下,Jigsaw为模块提供了明确、可靠的可访问性模型。
问题4:单片运行时
Java运行时在单片的rt.jar中。在我的机器上,它有60多MB和20k个类!在微服务、IoT设备等时代,如果不使用Corba、Swing、XML和其他库,则将其保存在磁盘上是不可取的。
Jigsaw将JDK本身分成许多模块,例如java.sql包含熟悉的SQL类。这样做有几个好处,但新的好处是jlink工具。假设应用程序完全模块化,jlink会生成一个可分发的运行时映像,其中只包含指定的模块(及其依赖项)。展望未来,Oracle设想JDK模块被预先编译为本机代码。尽管jlink是可选的,AOT编译是试验性的,但它们都是Oracle前进方向的重要指示。
问题5:版本控制
众所周知,类路径不允许我们使用相同jar的多个版本,例如bar-lib-1.1.jarbar-lib-2.2.jar
Jigsaw并未解决这个问题;Mark Reinhold在这里的原因中说明了这一点。要点是,Maven、Gradle和其他工具构成了一个庞大的依赖管理生态系统,另一个解决方案将会带来更多的负面影响而非好处。
需要注意的是,其他解决方案(例如OSGi)确实解决了这个问题(以及#4之外的其他问题)。 底线 这是Jigsaw的一些关键点,由特定问题驱动。
请注意,解释Jigsaw、OSGi、JBoss Modules等之间的争议是另一个讨论,属于另一个Stack Exchange网站。这些解决方案之间的差异比这里描述的更多。此外,已经有足够的共识批准了JSR 376的公共审查重新考虑投票

3
本文详细解释了OSGi和JPMS/Jigsaw尝试解决的问题: "Java 9, OSGi and the Future of Modularity" [2016年9月22日]
它还深入探讨了OSGi和JPMS/Jigsaw的方法。目前为止,与成熟(16岁)的OSGi相比,作者几乎没有列出JPMS/Jigsaw的实际优点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接