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声明“请不要使用此程序包中的类型”的程序包命名为“ impl”或“内部”。 不幸的是,生活不会那么简单。
但是,创建模块相对简单。 模块通常只是一个jar文件,其根目录具有module-info.class
文件-称为模块化jar文件 。 该文件是从您的源数据库中的module-info.java
文件创建的(请参阅下面的更多信息)。
使用模块化jar文件涉及将jar文件添加到modulepath而不是classpath中。 如果模块化jar文件位于类路径上,则它将根本不充当模块,并且module-info.class
将被忽略。 这样,虽然modulepath上的模块化jar文件将具有由JVM强制执行的隐藏包,但classpath上的模块化jar文件将根本没有隐藏包。
其他模块系统
Java历史上有其他模块系统,最著名的是OSGi和JBoss模块。 重要的是要了解JPMS与这些系统几乎没有相似之处。
没有JVM的直接支持,OSGi和JBoss模块都必须存在,但仍然为模块提供一些其他支持。 这是通过在自己的类加载器中启动每个模块来实现的,该技术可以完成任务,但并非没有问题。
毫不奇怪,鉴于这些是现有的模块系统,因此来自这些小组的专家已被纳入开发JPMS的正式专家小组中。 但是,这种关系并不和谐。 基本上,JPMS作者(Oracle)已着手构建JVM扩展,该扩展可用于可描述为模块的事物,而现有的模块系统则从大型应用程序中的实际用例和棘手的边缘情况中获得经验和价值。今天存在。
在阅读有关模块的内容时,重要的是要考虑您正在阅读的文章的作者是否来自OSGi / JBoss Modules设计阵营。 (尽管我曾经使用过Eclipse和其他内部使用OSGi的工具,但我从未积极使用OSGi或JBoss模块。)
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强制)时,所有其他软件包均被隐藏。
对JDK的核心模块java.base存在隐式依赖。 请注意,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
依赖于(要求) org.joda.convert
。 与简单的“ requires”相对,“ requirestransive”意味着任何需要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
,但是仅在编译时才“需要静态”,而不是简单的“需要”。 这是一个可选的依赖项。 如果番石榴在模块路径上,则Joda-Convert将能够看到和使用它,并且如果不存在番石榴,则不会发生任何错误。 这由虚线示出。
访问规则
在应用了JVM访问规则的模块路径上运行模块化jar时,如果满足以下条件,则程序包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
不到代码,因为没有模块依赖性。
有什么问题吗?
上面描述的基础很简单。 最初很容易想象如何从这些基础上构建应用程序并从隐藏包中受益。 不幸的是,很多事情都会出错。
1)仅当在modulepath上使用模块化jar时,才适用module-info文件。 为了兼容性,类路径上的所有代码都打包为一个特殊的未命名模块 ,没有隐藏的包,并且可以完全访问整个JDK。 因此,隐藏包的安全性优势至多是微不足道的。 但是,JDK本身的模块始终以模块化模式运行,因此始终可以保证安全性。
2)不处理模块的版本。 您不能两次加载相同的模块名称-您不能两次加载同一模块的两个版本。 它完全由您自己决定,然后由您的构建工具决定,以创建可以实际运行的一组连贯的模块。 因此,无法解决由版本冲突导致的类路径地狱情况。 请注意,将版本号放在模块名称中是一个坏主意,它不能解决此问题并会创建其他问题。
3)两个模块不能包含相同的包装。 在您认为它也适用于隐藏包之前,这似乎非常明智。 由于隐藏的软件包未在module-info.class
列出,因此Maven之类的工具必须解压缩jar文件才能发现其中存在哪些隐藏的软件包,以警告冲突。 作为库的用户,这样的冲突将完全令人惊讶,因为您不会在Javadoc中看到任何隐藏的包。 这是一个更普遍的迹象,表明JPMS无法在模块之间提供足够的隔离,原因目前尚不清楚。
4)在编译时和运行时,模块之间必须没有周期。 再次,这似乎是明智的–谁想让模块A依赖于B依赖于C依赖于A? 但是现有项目的现实是这种情况会发生,并且在类路径上也不是问题。 例如,考虑上例中Guava决定依赖Joda-Convert会发生什么。 这种限制将使一些现有的开源项目难以移植。
5)反思正在发生变化,因此非公共领域和方法将不再可以通过反思来访问。 由于几乎每个框架都以这种方式使用反射,因此迁移现有代码将需要大量工作。 特别是,JDK将非常难以锁定以防止反射,这可能会很痛苦(命令行标志暂时可以逃脱陷阱)。 本文没有机会探讨模块声明如何影响反射–有关更多详细信息,请参见幻灯片中的“打开”。
6)您的依赖项是否模块化? 从理论上讲,只有在所有依赖项也是模块之后,您才能将代码转换为模块。 对于具有数百个jar文件依赖项的任何大型应用程序,这将是一个问题。 “解决方案”是自动模块 ,其中放置在modulepath上的普通jar文件会自动转换为模块。 这个过程是有争议的,命名是一个大问题。 库作者不应将依赖自动模块的模块发布到Maven Central之类的公共存储库,除非他们具有“自动模块名称”清单条目。 再次,自动模块应有各自的特色!
7)模块命名尚未确定。 我已经开始相信, 在模块包含的最高软件包之后命名模块(这是使该模块“获得”子软件包的所有权)是唯一明智的策略。
8)与构建系统冲突–谁负责? Maven pom.xml
还包含有关项目的信息。 是否应该扩展它以允许添加模块信息? 我不建议这样做,因为module-info.java
文件包含您API的绑定部分,最好用.java代码表示,而不是像pom.xml
这样的元数据表示。
对于那些想要更深入地阅读本书的人,请尝试Nicolai的这本书。
摘要
不要对JPMS – Java 9中的模块感到兴奋。以上只是对module-info.java
文件的可能情况以及module-info.java
的限制的概述。 如果您打算对您的库或应用程序进行模块化,请稍等片刻,直到一切变得更加清晰。
本文最初发表在Stephen Colebourne的博客上 。
Stephen Colebourne将在伦敦的JAX上发表一个演讲,他将介绍Java平台模块系统的基础及其含义,特别是对反思的影响。
翻译自: https://jaxenter.com/java-9-modules-jpms-basics-135885.html