模块化是在包之上添加的更高级别的聚合。这个新的与元素的关键字是module–唯一的命名,可重用的相关包组,和资源文件是一样的(例如图片,xml文件)。下面是module的结构描述:
- the module’s name 模块的名字
- the module’s dependencies(that is, other modules this module depends on) 依赖模块
- the packages it explicitly makes available to other modules(all other packages in the module are implicitly unavailable to other mudules) 显式的向其他模块提供的包(模块中所有其他的包都是隐式的,不可用到其他模块)
- the services it offers 提供的服务
- the services it consumes 它使用的服务
- to what other modules it allows reflection 它允许反射的其他模块
Java SE平台是从1995年建立的。现在,大约有1000万开发人员使用它来构建从资源受限设备上的小应用程序–比如物联网和其他嵌入式设备–到大规模的业务关键型和任务关键型的系统。目前有大量遗留代码,但是知道现在,Java平台基本都是一个单一的通用的解决方案。多年来,各种各样的努力都是模块化Java的,但是没有一个被广泛使用,而且没有一个可以被用来模块化Java平台。
模块化Java SE平台一直是一个挑战,而且这个过程已经花费了很多年时间。JSR 277:Java Module System最初是2007年在Java7中提出来的。接着JSR被JSR 376:Java Platform Module System所取代,并且将Java8作为目标。Java SE平台现在在Java 9中模块化,但直到Java 9被推迟到2017年9月才开始。
Each module must explicitly state its dependencies. 每一个模块必须显式地声明它的依赖关系
根据JSR 376, 模块化Java SE平台的主要目标如下:
- 稳定的配置——模块化提供了一种机制,可以通过在编译时和执行时识别的方式显式地声明模块之间的依赖关系。关系可以遍历这些依赖关系,以确定所需要的所有模块的子集。
- 强大的封装——只有模块显式导出的包才能被其他模块使用。另一个模块不能使用这些包,除非它显式地声明它需要其他模块的功能。这提升了平台的安全性,使得更少的类存在潜在的攻击者。你可能会发现,考虑模块化帮助你想出更简洁更合理的设计。
- 可伸缩的Java平台——以前,Java平台是由大量的包组成的庞然大物,使发展、维护和开发具有挑战性。它不能够被细分。该平台现在已模块化为95个模块(随着java的发展,这个数字可能会变)。您可以自定义运行时,只包含你的app或者设备需要的模块。例如,如果设备不支持GUI,你可以创建运行时不包含GUI模块,显著地减小运行时大小。
- 更好的平台完整性——在Java9之前,可以在平台上使用许多不适合应用程序使用的类。通过强大的封装,这些内部API真正的封装了,并且隐藏在使用这个平台的应用程序里面。如果您的代码依赖于内部API, 那么迁移遗留代码到模块化的Java 9会有问题。
- 提升的性能——JVM使用各种优化技术来提升应用的性能。JSR 376 表明,这些技术在预先知道所需类型仅位于特定模块的情况下更有效。
Java 9 的一个关键方面是将JDK划分为模块,以支持各种配置。(查阅“JEP 200:The Modular JDK.” 所有Java模块化的JEP和JSR都展示在表1中)在JDK的bin文件夹使用Java命令行list-modules选项,如下:
java --list-modules
这个命令是列出JDK的模块集,其中包括实现Java语言SE规范(以java开头的名称)、JavaFX模块(以javafx开头的名称)、JDK特定模块(以jdk开头的名称)和Oracle-specific的标准模块(以oracle开头的名称)。每个模块后面都跟着一个版本@9表明模块属于Java 9。
A key motivation of the module system is strong encapsulation. 模块系统的主要动机是强封装
正如我们提到的,模块必须提供模块描述符——指定模块依赖关系的元数据、模块向其他模块提供的包以及更多的内容。模块描述符是模块声明的编译版本,它在module-info.java文件中被定义。每一个模块的声明都以关键字module开始,后面接一个唯一的模块名称和一个用括号括起来的模块体,如下所示:
module modulename{
}
模块声明的主体可以是空的,也可以包含这种模块指令,包括requires,exports,provides…还有uses和opens(下面会对每一个指令进行讨论)。稍后你将会看到,编译的模块声明创建模块描述符,模块声明存储在module-info.class文件中,这个文件在该模块的更目录下。
关键字 exports, module, open, opens, provides, requires, uses, with, 还有to和transitive, 我们稍后会介绍,都是被限制的关键字。它们仅在模块声明中是关键字,并且可以作为代码中其他地方的标识符使用。
requires.模块指令requires表明这个模块依赖另外一个模块——这种关系叫作模块依赖。每一个模块必须明确声明它的依赖关系。当模块A requires 模块B时,模块A被认为读取模块B, 模块B被认为由模块A读取。指定依赖模块时,使用requires,如下:
requires modulename;
requires static 指令表示该模块在编译时需要,但是在运行时是可选的。这个在可选的依赖里面有讲,在这篇介绍里面不会讨论。
requires transitive-implied readability. 要指定对另一个模块的依赖关系,并确保读取模块的其他模块也可以读取依赖项——这被称为隐藏的可读性——使用requires transitive, 如下:
requires transitive modulename;
考虑下面的来自java.desktop模块声明中的指令:
requires transitive java.xml;
在这个例子中,任何读取java.desktop模块的模块都会隐式的读取java.xml。例如,如果java.desktop模块中的一个方法返回类型来自于java.xml模块,模块中的代码,读取java.desktop的话,就会变得依赖java.xml。如果在java.desktop的模块声明中没有requires transitive指令的话,依赖模块将不会通过编译,除非他们明确地能读取java.xml。
根据JSR 379 Java SE 的标准模块必须在所有情况下都授予隐含的可读性,就像本文锁描述的那样。同时,尽管Java SE标准米快可能依赖于非标准模块,但是它不能赋予它们隐含的可读性。只依赖于Java SE标准模块的代码确保了代码在Java SE的实现中是可移植的。
exports and exports…to. exports模块指令指明模块中pulic和protected类型的包,应该可以在所有其他的模块中进行编码。exports…to指令允许您在一个逗号分隔的列表中指定哪个模块或模块的代码可以访问输出的包——这被称为一个合格的输出。
use.use模块指令指明该模块使用的服务——使这个模块成为服务的消费者。在uses指令中的服务是一个类的对象,它实现了接口,或者继承了抽象类。
open, opens, and opens…to.在Java 9 之前,反射可以被用来知道包里面的所有类型和类型的所有成员,甚至包括私有成员,无论你是希望有这种功能还是没有。因此,它并没有真正的封装。
模块系统的关键之处在于强大的封装。默认地,模块里面的类型对其他模块是不可见的,除非它是公共的类型并且你对这个包使用了export指令。你只暴露你想暴露的 包。在Java 9中,这同样适用于反射。
只在运行时允许对包可见。opens模块的指令格式如下:
opens package
这个指令表明包的public类型(包括内嵌的public和protected类型)只在运行时对其他模块的代码可见。同时,指定包中的所有类型(和这些类型的所有成员)都可以通过反射访问。
允许通过指定模块在运行时访问包。opens…to模块指令的格式如下:
opnes package to comma-separated-list-of-modules
表明一个特定的包的public类型(及其嵌套的public和protected类型)可以在运行时仅在列出的模块中访问。指定包中的所有类型(以及所有类型的成员)都可以通过对指定模块中的代码进行反射访问。
运行时允许访问模块中的所有包。如果给定模块中的所有包都可以在运行时访问,并通过反射对所有其他模块进行反射,那么您可以打开整个模块,如:
open module modulename{
// module directives
}
默认情况下,带有运行时反射访问包的模块可以看到包的public类型(以及它们嵌套的public和protected类型)。然而,其他模块中的代码能够访问暴露的包中这些类型中的所有类型和所有成员,包括通过setAccessible来访问私有的成员,就像早起的java版本一样。
关于反射和setAccessible的信息,可以看Oracle’s documentation.
在剩下的文章中,可以了解到更多的关于默认反射和创建一个简单的app来展示module的基本原理。