Java平台模块系统(JPMS)是Java SE 9的主要新特性。 在本文中,我将介绍它,把我的大部分观点留给后续的文章。 这是根据这些幻灯片编写的。
Java平台模块系统(JPMS)
新的模块系统是作为Project Jigsaw开发的,其目的是提高Java中编码的抽象水平,具体如下。
本项目的主要目标是:
* 使Java SE平台和JDK更容易扩展到小型计算设备;
* 提高整个Java SE平台实现的安全性和可维护性,特别是JDK;
* 使应用性能得到提高;
* 使开发者更容易构建和维护Java SE和EE平台的库和大型应用。
为了实现这些目标,我们建议为Java SE平台设计和实现一个标准模块系统,并将该系统应用于平台本身和JDK。这个模块系统应该足够强大,可以将JDK和其他大型遗留代码库模块化,但仍然可以为所有开发人员所用。
然而,正如我们将看到的,项目目标并不总是能够实现。
什么是JPMS模块?
JPMS是对Java库、语言和运行时的改变,这意味着它影响到开发者日常编码的整个堆栈,因此JPMS可能会产生很大的影响。 由于兼容性的原因,大多数现有的代码可以在Java SE 9中忽略JPMS,这一点可能被证明是非常有用的。
需要掌握的关键概念是,JPMS给JVM增加了一个新的概念--模块。以前,代码被组织成字段、方法、类、接口和包,而在Java SE 9中,有一个新的结构元素--模块:
- 一个类是字段和方法的容器
- 包是类和接口的容器
- 一个模块是一个包的容器
因为这是一个新的JVM元素,它意味着运行时可以应用强大的访问控制。 在Java 8中,开发者可以通过声明一个类的方法为私有,来表达其他类不能看到这些方法。 在Java 9中,开发者可以表达一个包不能被其他模块看到,也就是说,一个包可以隐藏在一个模块中。
从理论上讲,能够隐藏包对于应用程序设计应该是一个很大的好处。 不再需要将一个包命名为 "植入 "或 "内部",并在Javadoc中声明 "请不要使用这个包中的类型"。 不幸的是,生活不会那么简单。
然而,创建一个模块是相对简单的。一个模块通常只是一个jar文件,它的根部有一个module-info.class 文件--被称为模块化jar文件。该文件是从你的源代码库中的module-info.java 文件创建的(更多细节见下文)。
使用模块化的jar文件需要将jar文件添加到modulepath而不是classpath中。 如果一个模块化的jar文件在classpath上,它将根本不作为一个模块,而module-info.class 将被忽略。因此,虽然modulepath上的模块化jar文件将有JVM强制执行的隐藏包,但classpath上的模块化jar文件根本不会有隐藏包。
其他模块系统
Java在历史上有其他的模块系统,最明显的是OSGi和JBoss Modules。 重要的是要理解JPMS与这些系统没有什么相似之处。
OSGi和JBoss Modules必须在没有JVM直接支持的情况下存在,但仍然为模块提供一些额外的支持。 这是通过在自己的类加载器中启动每个模块来实现的,这种技术能够完成工作,但也不是没有问题。
不足为奇的是,鉴于这些都是现有的模块系统,来自这些团体的专家已经被纳入开发JPMS的正式专家组。 然而,这种关系并不和谐。 从根本上说,JPMS的作者(Oracle)已经着手建立一个JVM扩展,可以用于可以被描述为模块的东西,而现有的模块系统从真正的用例和目前存在的大型应用中棘手的边缘案例中获取经验和价值。
在阅读有关模块的文章时,必须考虑你所阅读的文章的作者是否来自OSGi/JBoss模块设计阵营。(我从未积极使用过OSGi或JBoss模块,尽管我使用过Eclipse和其他内部使用OSGi的工具。
module-info.java
module-info.java 文件包含了定义模块的指令(这里涵盖了最重要的指令,但还有更多)。这是一个.java 文件,然而其语法与你以前见过的任何.java 文件完全不同。
你必须回答两个关键问题来创建这个文件--这个模块依赖什么,以及它输出什么:
module com.opengamma.util {
requires org.joda.beans; // this is a module name, not a package name
requires com.google.guava;
exports com.opengamma.util; // this is a package name, not a module name
}
(模块的名称需要单独写一篇文章,这篇文章我将使用包名风格)
这个模块声明说,com.opengamma.util 依赖(需要)org.joda.beans 和com.google.guava 。它导出了一个包,com.opengamma.util 。在使用模块路径时,所有其他包都被隐藏起来(由 JVM 强制执行)。
java.base ,JDK的核心模块。注意,JDK本身也是模块化的,所以如果你想依赖Swing、XML或Logging,需要表达这种依赖性。
module org.joda.beans {
requires transitive org.joda.convert;
exports org.joda.beans;
exports org.joda.beans.ser;
}
这个模块声明说org.joda.beans 依赖于(requests)org.joda.convert 。"requests transitive",而不是简单的 "requests",意味着任何需要org.joda.beans的模块也可以看到并使用来自org.joda.convert 的包。这里使用的是Joda-Beans的方法,其返回类型来自Joda-Convert。这一点用虚线表示。
module org.joda.convert {
requires static com.google.guava;
exports org.joda.convert;
}
这个模块声明说,org.joda.convert 依赖于(需要)com.google.guava ,但只是在编译时,"需要静态",而不是简单的 "需要"。如果Guava在模块路径上,那么Joda-Convert将能够看到并使用它,如果Guava不存在,则不会发生错误。 这由虚线显示。
访问规则
当运行模块路径上的模块化jar并应用JVM访问规则时,包A中的代码可以看到包B中的一个类型如果:
- 该类型是公共的
- 包B是从它的模块中导出的
- 从包含包A的模块到包含包B的模块之间存在依赖关系
因此,在上面的例子中,模块com.opengamma.util 中的代码可以看到包org.joda.beans,org.joda.beans,ser,org.joda.convert 和任何由Guava导出的包。然而,它不能看到包org.joda.convert.internal (因为它没有被导出)。此外,代码模块com.google.guava 不能看到包org.joda.beans 或org.joda.convert 的代码,因为没有模块依赖关系。
什么会出错呢?
上面描述的基础知识是很简单的。不幸的是,有很多事情会出错:
-
所有模块信息文件的使用只适用于在模块路径上使用模块化的 jars。 为了兼容,classpath 上的所有代码都被打包成一个特殊的未命名的模块,没有隐藏的包,可以完全访问整个 JDK。 因此,隐藏包的安全优势充其量是微不足道的。 然而,JDK 本身的模块总是以模块化模式运行,因此总是保证安全优势。
-
模块的版本没有被处理。你不能让同一个模块的名字加载两次--你不能让同一个模块的两个版本加载两次。这完全取决于你,也就是你的构建工具,来创建一个可以实际运行的连贯的模块集。 因此,由版本冲突引起的classpath hell情况并没有得到解决。 请注意,把版本号放在模块名称中是一个坏主意,它不能解决这个问题,还会产生其他问题。
-
两个模块不能包含相同的包。 这看起来非常合理,直到你考虑到它也适用于隐藏包。由于隐藏包没有在
module-info.class,像Maven这样的工具必须解压jar文件以发现有哪些隐藏包,以便警告冲突。 作为库的使用者,这样的冲突将是完全令人惊讶的,因为你在Javadoc中没有任何关于隐藏包的提示。 这更普遍地表明,JPMS没有在模块之间提供足够的隔离,原因目前还不清楚。 -
在编译时和运行时,模块之间必须没有循环。 同样,这似乎是合理的--谁想让模块A依赖B依赖C,而C又依赖A呢? 但现有项目的现实是,这种情况发生了,在classpath上不是问题。 例如,考虑如果Guava决定依赖上面例子中的Joda-Convert会发生什么。 这个限制将使一些现有的开源项目难以迁移。
5)反射正在发生变化,比如非公共字段和方法将不再能通过反射访问。 由于几乎每个框架都以这种方式使用反射,因此需要做大量的工作来迁移现有的代码。 特别是,JDK将对反射进行非常严格的封锁,这可能会证明是痛苦的(命令行标志可以暂时逃脱陷阱)。 本文还没有机会探讨模块声明如何影响反射--更多细节见幻灯片的 "打开"。
-
你的依赖关系是模块化的吗?理论上,只有当你的所有依赖关系也是模块时,你才能把你的代码变成模块。 对于任何有数百个jar文件依赖关系的大型应用来说,这将是一个问题。自动模块是 "解决方案",即把一个普通的jar文件放在模块路径上,就会自动变成一个模块。 这个过程有争议,命名是个大问题。 库作者不应该把依赖自动模块的模块发布到Maven Central等公共资源库,除非它们有自动模块-名称清单条目。同样,自动模块应该有自己的文章!
-
模块的命名还没有定型。我相信,用它所包含的最高软件包命名你的模块,使该模块 "拥有 "子软件包的所有权,是唯一合理的策略。
8)与构建系统的冲突--谁说了算?Maven的pom.xml 也包含项目的信息。是否应该扩展到允许添加模块信息?我建议不要,因为module-info.java 文件包含了你的API的绑定部分,而这最好用.java代码来表达,而不是像pom.xml 这样的元数据。
对于那些想要更深入阅读的书,可以试试Nicolai的这本。
总结
不要对JPMS--Java 9中的模块感到太兴奋。以上只是对module-info.java 文件的可能性和JPMS的限制的总结。如果你正在考虑将你的库或应用程序模块化,请再等等,直到一切变得更清晰。
欢迎反馈,但请记住,我正在计划更多的文章。