Java 9 终于要包含 Jigsaw 项目了

阅读数:11576 2016 年 2 月 2 日

话题:Java语言 & 开发架构

当 Jigsaw 在 Java 9 中最终发布时,这个项目的历史已经超过八年了。

在最初的几年中,它必须要与另外两个类似的 Java 规范请求(Java Specification Request)进行竞争,这两个规范名为JSR 277 Java 模块系统(Java Module System)以及JSR 294 增强的模块化支持(Improved Modularity Support)。它还导致了与 OSGi 社区的冲突,人们担心 Jigsaw 项目会成为不必要且不完备的功能性重复,逼迫 Java 开发人员必须在两种互不兼容的模块系统中做出选择。

在早期,这个项目并没有充足的人手,在 2010 年 Sun 并入 Oracle 的时候,甚至一度中断。直到 2011 年,在 Java 中需要模块系统的强烈需求被重申,这项工作才得到完全恢复。

接下来的三年是一个探索的阶段,结束于 2014 年的 7 月,当时建立了多项 Java 增强提议( Java Enhancement Proposal),包括JEP 200 模块化 JDK(Modular JDK)JEP 201 模块化源码(Modular Source Code)JEP 220 模块化运行时镜像(Modular Run-Time Image),以及最终的JSR 376 Java 平台模块系统(Java Platform Module System)。上述的最后一项定义了真正的 Java 模块系统,它将会在 JDK 中以一个新 JEP 的形式来实现。

在 2015 年 7 月,JDK 划分为哪些模块已经大致确定(参见 JEP 200),JDK 的源码也进行了重构来适应这种变化(参见 JEP 201),运行时镜像(run-time image)也为模块化做好了准备(参见 JEP 220)。所有的这些都可以在当前JDK 9 的预览版中看到。

针对 JSR 376 所开发的代码很快将会部署到 JDK 仓库中,但是令人遗憾的是,现在模块化系统本身尚无法体验。(目前,Java 9 的预览版本已经包含了模块化功能。——译者注)

驱动力

在 Jigsaw 项目的历史中,它的驱动力也发生过一些变化。最初,它只是想模块化 JDK。但是当人们意识到如果能够在库和应用程序的代码中也使用该工具的话,将会带来非常大的收益,于是它的范围得到了扩展。

不断增长且不可分割的 Java 运行时

Java 运行时的大小在不断地增长。但是在 Java 8 之前,我们并没有办法安装 JRE 的子集。所有的 Java 安装包中都会包含各种库的分发版本,如 XML、SQL 以及 Swing 的 API,不管我们是否需要它们,都要将其包含进来。

对于中等规模(如桌面 PC 和笔记本电脑)以上的计算设备来说,这不算是什么严重的问题,但是对于小型的设备来说,这就很严重了,比如在路由器、TV 盒子和汽车上,还有其他使用 Java 的小地方。随着当前容器化的趋势,在服务器领域也有相关的要求,因为减少镜像的大小就意味着降低成本。

Java 8 引入了 compact profile的功能,它们定义了三个 Java SE 的子集。在一定程度上缓解了这个问题,但是它们只有在严格限制的场景下才能发挥作用,profile 过于死板,无法涵盖现在和未来所有使用 JRE 部分功能的需求。

JAR/Classpath 地狱

JAR 地狱和 Classpath 地狱是一种诙谐的说法,指的是 Java 类加载机制的缺陷所引发的问题。尤其是在大型的应用中,它们可能会以各种方式产生令人痛苦的问题。有一些问题是因为其他的问题而引发的,而有一些则是独立的。

无法表述依赖

JAR 文件无法以一种 JVM 能够理解的方式来表述它依赖于哪些其他的 JAR。因此,就需要用户手动识别并满足这些依赖,这要求用户阅读文档、找到正确的项目、下载 JAR 文件并将其添加到项目中。

而且,有一些依赖是可选的,只有用户在使用特定功能的特性时,某个 JAR 才会依赖另外一个 JAR。这会使得这个过程更加复杂。

Java 运行时在实际使用某项依赖之前,并不能探测到这个依赖是无法满足的。如果出现这种情况,将会出现NoClassDefFoundError异常,进而导致正在运行的应用崩溃。

像 Maven 这样的构建工具能够帮助解决这个问题。

传递性依赖

一个应用程序要运行起来可能只需依赖几个库就足够了,但是这些库又会需要一些其他的库。问题组合起来会变得更加复杂,在所消耗的体力以及出错的可能性上,它会呈指数级地增长。

同样,构建工具能够在这个问题上提供一些帮助。

遮蔽

有时候,在 classpath 的不同 JAR 包中可能会包含全限定名完全相同的类,比如我们使用同一个库的两个不同版本。因为类会从 classpath 中的第一个 JAR 包中加载,所以这个版本的变种将会“遮蔽”所有其他的版本,使它们变得不可用。

如果这些不同的变种在语义上有所差别,那将会导致各种级别的问题,从难以发现的不正常行为到非常严重的错误都是有可能的。更糟糕的是,问题的表现形式是不确定的。这取决于 JAR 文件在 classpath 中的顺序。在不同的环境下,可能也会有所区别,例如开发人员的 IDE 与代码最终运行的生产机器之间就可能有所差别。

版本冲突

如果项目中有两个所需的库依赖不同版本的第三个库,那么将会产生这个问题。

如果这个库的两个版本都添加到 classpath 中的话,那么最终的行为是不可预知的。首先,因为前面所述的遮蔽问题,两个版本的类中,只会有一个能够加载进来。更糟糕的是,如果某个类位于一个 JAR 包中,但是它所访问的其他类却不在这个包中,这个类也能够加载。所导致的结果就是,对这个库的代码调用将会混合在两个版本之中。

在最好的情况下,如果试图访问所加载的类中不存在的代码,将会导致明显的NoClassDefFoundError错误。但是在最坏的情况下,版本之间的差别仅仅是在语义上,实际的行为会有细微的差别,这会引入很难发现的 bug。

识别这种情况所导致的难以预料的行为是很困难的,也无法直接解决。

复杂的类加载机制

默认情况下,所有的类由同一个ClassLoader负责加载,在有些场景下,可能有必要引入额外的加载器,例如允许用户加载新的类,对应用程序进行扩展。

这很快就会导致复杂的类加载机制,从而产生难以预期和难以理解的行为。

在包之间,只有很弱的封装机制

如果类位于同一个包中,那 Java 的可见性修饰符提供了一种很棒的方式来实现这些类之间的封装。但是,要跨越包之间边界的话,那只能使用一种可见性:public

因为类加载器会将所有加载进来的包放在一起,public 的类对其他所有的类都是可见的,因此,如果我们想创建一项功能,这项功能对某个 JAR 是可用的,而对于这个 JAR 之外是不可用的,这是没有办法实现的。

手动的安全性

包之间弱封装性所带来的一个直接结果就是,安全相关的功能将会暴露在同一个环境中的所有代码面前。这意味着,恶意代码有可能绕过安全限制,访问关键的功能。

从 Java 1.1 开始,有一种 hack 的方式,能够防止这种状况:每当进入安全相关的代码路径时,将会调用SecurityManager,并判断是不是允许访问。更精确地讲,它应该在每个这样的路径上都进行调用。过去,在有些地方遗漏了对它们的调用,从而出现了一些漏洞,这给 Java 带来了困扰。

启动性能

最后,Java 运行时加载并 JIT 编译全部所需的类需要较长的时间。其中一个原因在于类加载机制会对 classpath 下的所有 JAR 执行线性的扫描。类似的,在识别某个注解的使用情况时,需要探查 classpath 下所有的类。

目标

Jigsaw 项目的目标就是解决上面所述的问题,它会引入一个语言级别的机制,用来模块化大型的系统。这种机制将会用在 JDK 本身中,开发人员也可以将其用于自己的项目之中。

需要注意的是,对于 JDK 和我们开发人员来说,并不是所有的目标都具有相同的重要性。有一些与 JDK 具有更强的相关性,并且大多数都对日常的编程不会带来巨大的影响(这与最近的语言修改形成了对比,如 lambda 表达式和默认方法)。不过,它们依然会改变大型项目的开发和部署。

可扩展性的平台

JDK 在模块化之后,用户就能挑出他们需要的功能,并创建自己的 JRE,在这个 JRE 中只包含他们需要的模块。这有助于在小型设备和容器领域中,保持 Java 作为关键参与者的地位。

在这个提议的规范中,允许将 Java SE 平台及其实现分解为一组组件,开发人员可以把这些组件组装起来,形成自定义的配置,里面只包含应用实际需要的功能。—— JSR 376

可靠的配置

通过这个规范,某个模块能够声明对其他模块的依赖。运行时环境能够在编译期(compile-time)、构建期(build-time)以及启动期(launch-time)分析这些依赖,如果缺少依赖或依赖冲突的话,很快就会发生失败。

强封装

Jigsaw 项目的一个主要目标就是让模块只导出特定的包,其他的包是模块私有的。

模块中的私有类就像是类中的私有域。换句话说,模块的边界不仅确定了类和接口的可见性,还定义了它的可访问性。——Mark Reinhold 所撰写的文章“Project Jigsaw:将宏伟蓝图转换为可聚焦的点”

提升安全性和可维护性

在模块中,内部 API 的强封装会极大地提升安全性,因为核心代码对于没有必要使用它们的其余代码来讲是隐藏起来的。维护也会变得更加容易,这是因为我们能够更容易地将模块的公开 API 变得更小。

随意使用 Java SE 平台实现的内部 API 不仅有安全风险,而且也会带来维护的负担。该提议规范能够提供强封装性,这样实现 Java SE 平台的组件就能阻止对其内部 API 的访问。 —— JSR 376

提升性能

因为能够更加清晰地界定所使用代码的边界,现有的优化技术能够更加高效地运用。

很多预先(ahead-of-time)优化和全程序(whole-program)优化的技术会更加高效,因为能够得知某个类只会引用几个特定组件中的类,它并不能引用运行时所加载的任意类。 —— JSR 376

核心概念

因为模块化是目标,所以 Jigsaw 项目引入了模块(module)的概念,描述如下:

命名、自描述的程序组件,会包含代码和数据。模块必须能够包含 Java 类和接口,组织为包的形式,同时也能以动态加载库的形式(dynamically-loadable library)包含原生代码。模块的数据必须能够包含静态资源文件和用户可编辑的配置文件。 —— Java 平台模块系统:需求(草案 2)

为了能够基于一定的上下文环境来了解模块,我们可以想一下知名的库,如Google Guava Apache Commons中的库(比如 Collections 或 IO),将其作为模块。根据作者希望划分的粒度,每个库都可能划分为多个模块。

对于应用来说也是如此。它可以作为一个单体(monolithic)的模块,也可以进行拆分。在确定如何将其划分为模块时,项目的规模和内聚性将是重要的因素。

按照规划,在组织代码时,模块将会成为开发人员工具箱中的常规工具。

开发人员目前已经能够考虑到一些标准的程序组件,如语言层面的类和接口。模块将会是另外一种程序组件,像类和接口一样,它们将会在程序开发的各个阶段发挥作用。 ——Mark Reinhold 的文章“Project Jigsaw:将宏伟蓝图转换为可聚焦的点”

模块又可以进一步组合为开发阶段的各种配置,这些阶段也就是编译期、构建期、安装期以及运行期。对于我们这样的 Java 用户来说,可以这样做(在这种情况下,通常会将其称之为开发者模块),同时这种方式还可以用来剖析 Java 运行时本身(此时,它们通常称之为平台模块)。

实际上,这就是 JDK 目前进行模块化的规划。

(点击放大图像)

特性

那么,模块是如何运行的呢?查阅一下Jigsaw 项目的需求以及 JSR 376将会帮助我们对其有所了解。

依赖管理

为了解决“JAR/Classpath 地狱”的问题,Jigsaw 项目的一个关键特性就是依赖管理。让我们看一下这些相关的组件。

声明与解析

模块将会声明它需要哪些其他的模块才能编译和运行。模块系统会使用该信息传递性地识别所有需要的模块,从而保证初始的那个模块能够编译和运行。

我们还可以不依赖具体的模块,而是依赖一组接口。模块系统将会试图据此识别模块这些模块实现了所依赖的接口能够满足依赖系统会将其绑定到对应的接口中。

版本化

模块将会进行版本化。它们能够标记自己的版本(在很大程度上可以是任意格式,只要能够完全表示顺序就行),版本还能用于限制依赖。在任意阶段都能覆盖这两部分信息。模块系统会在各个阶段都强制要求配置能够满足所有的限制。

Jigsaw 项目不一定会支持在一个配置中存在某个模块的多个版本。但是,稍等,那该如何解决 JAR 地狱的问题呢? 好问题!

版本选择——针对同一个模块,在一组不同版本中挑选最合适的版本——并没有作为规范所要完成的任务。所以,在我撰写的上文中,模块系统会识别所需的模块进行编译,在运行时则可能会使用另外一个模块,这都基于一个假设,那就是环境中只存在模块的一个版本。如果存在多个版本的话,那么上游的步骤(如开发人员或者他所使用的构建工具)必须要做出选择,系统只会校验它能满足所有的约束。

封装

模块系统会在各个阶段强制要求强封装。这是围绕着一个导出机制实现的,在这种情况下,只有模块导出的包才能访问。封装与SecurityManager所执行的安全检查是相独立的。

这个提议的具体语法尚没有定义,但是JEP 200提供了一些关键语义的 XML 实例。作为样例,如下的代码声明了java.sql模块。

复制代码
<module>
<!-- 模块的名字 -->
<name>java.sql</name>
<!-- 每个模块都会依赖 java.base -->
<depend>java.base</depend>
<!-- 这个模块依赖于 java.logging 和 java.xml
模块,并重新导出这些模块所导出的 API 包 -->
<depend re-exports="true">java.logging</depend>
<depend re-exports="true">java.xml</depend>
<!-- 这个模块导出 java.sql、javax.sql 以及
javax.transaction.xa 包给其他任意的模块 -->
<export><name>java.sql</name></export>
<export><name>javax.sql</name></export>
<export><name>javax.transaction.xa</name></export>
</module>

从这个代码片段我们可以看出,java.sql 依赖于java.basejava.logging以及java.xml。在稍后介绍不同的导出机制时,我们就能理解上文中其他的声明了。

导出

模块会声明特定的包进行导出,只有包含在这些包中的类型才能导出。这意味着其他模块只能看到和使用这些类型。更严格是,其他模块必须要显式声明依赖包含这些类型的模块,这些类型才能导出到对应的模块中

非常有意思的是,不同的模块能够包含相同名称的包,这些模块甚至还能够将其导出

在上面的样例中,java.sql导出了java.sqljavax.sql以及javax.transaction.xa这些包。

重新导出

我们还能够在某个模块中重新导出它所依赖的模块中的 API(或者是其中的一部分)。这将会对重构提供支持,我们能够在不破坏依赖的情况下拆分或合并模块,因为最初的依赖可以继续存在。重构后的模块可以导出与之前相同的包,即便它们可能不会包含所有的代码。在极端的情况下,有一种所谓的聚合器模块(aggregator module),它可以根本不包含任何代码,只是作为一组模块的抽象。实际上,Java 8 中所提供的 compact profile 就是这样做的。

从上面的例子中,我们可以看到java.sql重新导出了它依赖的 API,即java.loggingjava.xml

限制导出

为了帮助开发者(尤其是模块化 JDK 的人员)让他们所导出 API 的有较小的接触面,有一种可选的限制导出(qualified export)机制,它允许某个模块将一些包声明为只针对一组特定的模块进行导出。所以使用“标准”机制时,导出功能的模块并不知道(也不关心)谁会访问这些包,但是通过限制导出机制,能够让一个模块限定可能产生的依赖。

配置、阶段以及保真性(Fidelity)

如前所述,JEP 200 的目标之一就是模块能够在开发的各个阶段组合为各种配置。对于平台模块可以如此,这样就能够创建与完整 JRE 或 JDK 类似的镜像,Java 8 所引入的 compact profile 以及包含特定模块集合(及其级联依赖)的任意自定义配置都使用了这种机制。类似的,开发人员也可以使用这种机制来组合他们应用程序的不同变种。

编译期(compile-time),要编译的代码只能看到所配置的模块集合中导出的包。在构建期(build-time),借助一个新的工具(可能会被称为JLink),我们能够创建只包含特定模块及其依赖的二进制运行时镜像。在安装期(launch-time),镜像能够看起来就像是只包含了它所具有的模块的一个子集。

我们还能够替换实现了授权标准(endorsed standard) 独立技术(standalone technology)的模块,在任意的阶段都能将其替换为较新的版本。这将会替代已废弃的授权标准重载机制(endorsed standards override mechanism)以及扩展机制(参见下文。)

模块系统的各个方面(如依赖管理、封装等等),在所有阶段的运行方式是完全相同的,除非因为特定的原因,在某些阶段无法实现。

模块相关的所有信息(如版本、依赖以及包导出)都会在代码文件中进行描述,这样会独立于 IDE 和构建工具。

性能

全程序优化的技术

在模块系统中,借助强封装技术,能够很容易自动计算出一段特定的代码都用在了哪些地方。这会使得程序分析和优化技术更加可行:

快速查找 JDK 和应用程序的类;及早进行字节码的检验;积极级联(aggressive inlining)像 lambda 表达式这样的内容以及其他的编译器优化;构建特定于 JVM 的内存镜像,它加载时能够比类文件更加高效;预先将方法体编译为原生代码;移除没有用到的域、方法和类。——Jigsaw 项目: 目标 & 需求(草案 3)

有一些被称为全程序优化(whole-program optimization)的技术,在 Java 9 中至少会实现两种这样的技术。还有包含一个工具,使用这个工具能够分析给定的一组模块,并使用上述的优化技术,创建更加高性能的二进制镜像。

注解

目前,要自动发现带有注解的类(如 Spring 注解标注的配置类),需要扫描特定包下的所有类。这通常会在程序启动的时候完成,这在相当程度上会减慢启动的过程。

模块将会提供一个 API,允许调用者识别所有带有给定注解的类。一种预期的方式是为这样的类创建索引,这个索引会在模块编译的时候创建。

与已有的概念和工具集成

诊断工具(如栈跟踪信息)将会进行更新,其中会包含模块的信息。而且,它们还会集成到反射 API 中,这样就能按照操作类的方式来使用它们,还会包含版本信息,这一信息可以进行反射,也可以在运行时重载

模块的设计能够让我们在使用构建工具时“尽可能地减少麻烦(with a minimum of fuss)”。编译之后的模块能够用在 classpath 中,也能作为一个模块来使用,这样的话,库的开发人员就没有必要为 classpath 应用和基于模块的应用分别创建多个构件了。

与其他模块系统的相互操作也进行了规划,这其中最著名的也就是 OSGi。

尽管模块能够对其他的模块隐藏包,但是我们依然能够对模块包含的类和接口执行白盒测试

特定 OS 的包

模块系统在设计时,始终考虑到了包管理器文件格式,“如 RPM、Debian 以及 Solaris IPS”。开发人员不仅能够使用已有的工具将一组模块集合创建为特定 OS 的包,这些模块还能调用按照相同机制安装的其他模块。

开发人员还能够将组成应用的一组模块打包为特定 OS 的包,“终端用户能够按照目标系统的通用做法,安装和调用所打成的包”。基于上述的介绍,我们可以得知只有目标系统中不存在的模块才必须要打包进来。

动态配置

正在运行中的应用能够创建、运行并发布独立的模块配置。在这些配置中,可以包含开发者和平台模块。对于容器类架构,这会非常有用,如 IDE、应用服务器或其他 Java EE 平台。

不兼容性

按照 Java 的惯例,这些变更在实现时,会强烈关注到向后的兼容性,所有标准和非废弃的 API 及机制都能够继续使用。但是项目可能会依赖其他缺乏文档的构造,这样的话,在往 Java 9 迁移的时候,就需要一些额外的工作了。

内部 API 不可用了

借助于强封装,每个模块能够明确声明哪些类型会作为其 API 的一部分。JDK 将会使用这个特性来封装所有的内部 API,因此它们会变得不可用了。

在 Java 9 所带来的不兼容性中,这可能是涵盖范围最大的一部分。但是这也是最明显的,因为它会导致编译错误。

那么,什么是内部 API 呢?毫无疑问,位于sun.*包中的所有内容。如果位于com.sun.*包中,或者使用了@jdk.Exported注解,在 Oracle JDK 中它依然是可用的,如果没有注解的话,那么它就是不可用的了。

能产生特殊问题的一个样例就是sun.misc.Unsafe类。它用在了很多项目中,用来实现关键任务或性能要求较高的代码,它将来可能不可用引发了很多相关讨论。不过,在一次相关的交流中曾经提出,通过一个废弃的命令行标记,它依然是可用的。考虑到无法将其所有的功能都放到公开 API 中,这可能是一种必要的权衡。

另外一个样例是com.sun.javafx.*包中的所有内容。这些类对于构建 JavaFX 控件是至关重要的,并且它们还有一定数量的 bug 要修改。这些类中的大多数功能都会作为发布的目标

合并 JDK 和 JRE

在具有可扩展的 Java 运行时之后,它允许我们很灵活地创建运行时镜像,JDK 和 JRE 就丧失了其独有的特性,它们只是模块组合中的两种形式而已。

这意味着,这两个构件将会具有相同的结构,包括目录结构也相同,任何依赖它(如在原来的 JDK 目录中会有名为 jre 的子目录)的代码就不能正常运行了。

内部 JAR 不可用了

lib/rt.jarlib/tools.jar这样的内部 JAR 将不可用了。它们的内容将会存储到特定实现的文件中,这些文件的格式还未明确说明,有可能会发生变化。

任何假设这些文件存在的代码将无法正确运行。这可能对 IDE 或其他严重依赖这些文件的工具带来一些切换的麻烦。

针对运行时镜像内容的新 URL 模式

在运行时,有些 API 会返回针对类和资源文件的 URL(如 ClassLoader.getSystemResource)。在 Java 9 之前,它们都是jar URL,格式如下:

复制代码
jar:file:<path-to-jar>!<path-to-file-in-jar>

Jigsaw 项目将会使用模块作为代码文件的容器,单个 JAR 将不可用了。这需要一个新的格式,所以这些 API 将会返回jrt URL

复制代码
jrt:/<module-name>/<path-to-file-in-module>

如果使用这些 API 所返回的实例来访问文件的代码(如 URL.getContent),那么运行方式会和现在一样。但是,如果依赖于 jar URL 的结构(比如手动构建它们或对其进行解析),那么就会出错了。

移除授权标准重载机制

有一些 Java API 被称为“独立技术(Standalone Technology)”,它们的创建是在 Java Community Process(如 JAXB)之外的。对它们来说,有可能会升级其依赖或使用替代实现。授权标准重载机制允许在 JDK 中安装这些标准的替代版本。

这种机制在 Java 8 中已经废弃了,在 Java 9 中将会移除,会由上文所述的可升级模块来替代。

移除扩展机制

借助扩展机制,自定义 API 能够被 JDK 中运行的所有应用程序使用,而不必在 classpath 中对其进行命名。

这种机制在 Java 8 中已经废弃了,在 Java 9 中将会移除。有些本身有用的特性将会保留。

接下来要做什么?

我们已经简要了解了 Jigsaw 项目的历史,看到是什么在驱动它的发展并讨论了它的目标,如何通过一些特性来实现这些目标。除了等待 Java 9 以外,我们还能做些什么呢?

准备

我们应该为自己的项目做一些准备工作,检查它们是否依赖 Java 9 中将要移除的内容。

至少,在检查内部 API 依赖方面不再需要手动搜索了。从 Java 8 开始,JDK 包含了 Java 依赖分析工具(Java Dependency Analysis Tool),名为JDeps介绍了一些内部的包,官方有针对Windows以及 Unix的文档),它能够列出某个项目依赖的所有的包。如果在运行时使用 -jdkinternals参数的话,那么它将会列出该项目所使用的几乎所有的内部 API。

之所以说“几乎所有”是因为它还无法识别 Java 9 中不可用的所有的包。这至少会影响到 JavaFX 所属的包,可以查看 JDK-8077349。(通过使用这个搜索,除了缺失的功能以外,我没能发现其他的缺陷。)

至少存在三个用于 Maven 的 JDeps 插件:分别由ApachePhilippe Marschall以及我本人所提供。就目前来讲,最后一个是唯一当jdeps -jdkinternals报告中依赖内部 API 时,导致构建失败的插件。(现在,Apache 的插件在出现内部 API 依赖时,也会提示构建失败,参见 InfoQ 的这篇新闻。——译者注)

讨论

Jigsaw 项目最新的消息来源于Jigsaw-Dev 邮件列表。我也会在博客中继续讨论这个话题。

如果你担心某个特定的 API 在 Java 9 中不可用的话,那么你可以查看相关 OpenJDK 项目的邮件列表,因为他们会负责开发公开的版本。

采用

Java 9 的早期试用构建版本已经可用了。不过,JSR 376 依然处于开发阶段,在这些构建版本中尚无法使用模块系统,还会有很多的变化。(在目前的试用版中,已经包含了 Jigsaw,不过最新的消息是 Java 9 又要延期六个月发布了。——译者注)实际上,除了强封装以外,其他的功能都已经就绪了。

将收集到的消息发送给 Jigsaw-Dev 邮件列表能够反馈给项目。最后,引用 JEP 220(临近)结尾的一段话:

我们不可能抽象地确定这些变更的全部影响,所以必须要依赖广泛的内部测试,尤其重要的还有外部测试。[……] 如果有些变更会给开发人员、部署人员或终端用户带来难以承受的负担,那么我们将会研究减少其影响的方式。

另外,还有一个全球的 Java 用户群组AdoptOpenJDK,它能够很好地将早期试用者联系起来。

关于作者

Nicolai Parlog是一名软件开发人员,对 Java 充满热情。他不断地阅读、思考以及撰写与 Java 相关的内容,他靠编码为生,也以此为乐。他是多个开源项目的长期贡献者,并在CodeFX上用博客记录软件开发相关的内容。你可以在Twitter上关注 Nicolai。

查看英文原文:Project Jigsaw is Really Coming in Java 9