d所撰写的文章“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.base、java.logging以及java.xml。在稍后介绍不同的导出机制时,我们就能理解上文中其他的声明了。
导出
模块会声明特定的包进行导出,只有包含在这些包中的类型才能导出。这意味着其他模块只能看到和使用这些类型。更严格是,其他模块必须要显式声明依赖包含这些类型的模块,这些类型才能导出到对应的模块中。
非常有意思的是,不同的模块能够包含相同名称的包,这些模块甚至还能够将其导出。
在上面的样例中,java.sql导出了java.sql、javax.sql以及javax.transaction.xa这些包。
重新导出
我们还能够在某个模块中重新导出它所依赖的模块中的API(或者是其中的一部分)。这将会对重构提供支持,我们能够在不破坏依赖的情况下拆分或合并模块,因为最初的依赖可以继续存在。重构后的模块可以导出与之前相同的包,即便它们可能不会包含所有的代码。在极端的情况下,有一种所谓的聚合器模块(aggregator module),它可以根本不包含任何代码,只是作为一组模块的抽象。实际上,Java 8中所提供的compact profile就是这样做的。
从上面的例子中,我们可以看到java.sql重新导出了它依赖的API,即java.logging和java.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中至少会实现两种这样的技术。还有包含一个工具,使用这个工具能够分析给定的一组模块,并使用上述的优化技术,创建更加高性能的二进制镜像。
注解
目前,要自动发现带有注解