14.5 使用多个模块
你现在已经掌握了如何建立一个单模块的应用,是时候使用多个模块做一些更接近现实情况的事儿了。你想让你的开支管理应用从数据源读取数据。为了达到这个目的,需要引入一个新的模块expenses.readers,它封装了对应的操作。借助Java 9的exports和requires子句,可以对现有两个模块expenses.application和expenses.readers之间的交互进行设置。
14.5.1 exports子句
下面是声明expenses.readers的一个示例(暂时不用担心那些看不懂的语法和概念,稍后会逐一介绍)。
module expenses.readers {exports com.example.expenses.readers; (以下3行)这些都是包名,并非模块名exports com.example.expenses.readers.file;exports com.example.expenses.readers.http;}
这段声明中引入了一个新东西:exports子句,由它声明的这些包会变为公有类型,可以被其他模块访问和调用。默认情况下,模块中的所有内容都是被封装的。模块系统使用白名单的方式帮助你进行更严格的封装控制,因此你需要显式地声明你愿意将哪些内容提供给别的模块访问(这种方式可以避免你由于偶然的机会开放一些内部接口给外部使用,因为这些接口如果几年后被某些黑客破解,可能导致你的系统被攻破)。
你的项目现在包含了两个模块,其目录结构如下:
|─ expenses.application
|─ module-info.java|─ com|─ example|─ expenses|─ application|─ ExpensesApplication.java
|─ expenses.readers
|─ module-info.java|─ com|─ example|─ expenses|─ readers|─ Reader.java|─ file|─ FileReader.java|─ http|─ HttpReader.java
14.5.2 requires子句
此外,你还可以像下面这样定义module-info.java:
module expenses.readers {requires java.base; ←---- 这是模块名,并非包名exports com.example.expenses.readers; (以下3行)这是包名,并非模块名exports com.example.expenses.readers.file;exports com.example.expenses.readers.http;}
这里新增的元素是requires子句,通过它你可以指定本模块对其他模块的依赖。默认情况下,所有的模块都依赖于名叫java.base的平台模块,它包含了Java主要的包,比如net、io和util。默认情况下,这个模块总是需要的,因此你不需要显式声明(这个就跟Java语言中,class Foo { … }等价于class Foo extends Object { … }一样)。
如果你需要导入java.base之外的其他模块,requires子句就必不可少了。
requires和exports子句的组合使得Java 9中的访问控制变得更复杂了。表14-2总结了Java 9之前与之后使用不同的访问修饰符时在对象可见性上的差异。
表 14-2 Java 9在类的可见性上提供了更细粒度的控制
| 类的可见性 | Java 9之前 | Java 9之后 |
|---|---|---|
| 任何人都可以访问所有的类 | ✓✓ |
✓✓(结合exports和requires子句)
|
| 有限的类可以公有访问 | ✗✗ |
✓✓(结合exports和requires子句)
|
| 仅在模块内部可以公有访问 | ✗✗ |
✓ (不需要exports子句)
|
| 受保护的 | ✓✓ | ✓✓ |
| 包内可见 | ✓✓ | ✓✓ |
| 私有的 | ✓✓ | ✓✓ |
14.5.3 命名
现在是时候讨论如何命名模块了。我们会以一个比较短的名字为例进行介绍(譬如expenses.application),这样做主要是为了避免混淆模块与包的命名(一个模块可以导出多个包)。不过,对于模块和包的命名,推荐的命名规范是不一样的。
Oracle公司推荐大家在命名模块时采用与包同样的方式,即互联网域名规范的逆序(譬如,com.iteratrlearning.training)。此外,模块名应该与它导出的主要API的包名保持一致,包名也应该遵循同样的规则。如果模块中并不存在这样的包,或者出于某些别的原因模块的命名不能直接与它导出的包对应,那么这种情况下模块名也应以互联网域名规范同样的逆序方式设计,并在其中插入作者的名字。
现在你已经了解了如何构建一个多模块的项目,不过该怎样打包并运行它呢?别急,这些内容会在下一节中介绍。
