14.5 使用多个模块

你现在已经掌握了如何建立一个单模块的应用,是时候使用多个模块做一些更接近现实情况的事儿了。你想让你的开支管理应用从数据源读取数据。为了达到这个目的,需要引入一个新的模块expenses.readers,它封装了对应的操作。借助Java 9的exportsrequires子句,可以对现有两个模块expenses.applicationexpenses.readers之间的交互进行设置。

14.5.1 exports子句

下面是声明expenses.readers的一个示例(暂时不用担心那些看不懂的语法和概念,稍后会逐一介绍)。

  1. module expenses.readers {
  2. exports com.example.expenses.readers; (以下3行)这些都是包名,并非模块名
  3. exports com.example.expenses.readers.file;
  4. exports com.example.expenses.readers.http;
  5. }

这段声明中引入了一个新东西:exports子句,由它声明的这些包会变为公有类型,可以被其他模块访问和调用。默认情况下,模块中的所有内容都是被封装的。模块系统使用白名单的方式帮助你进行更严格的封装控制,因此你需要显式地声明你愿意将哪些内容提供给别的模块访问(这种方式可以避免你由于偶然的机会开放一些内部接口给外部使用,因为这些接口如果几年后被某些黑客破解,可能导致你的系统被攻破)。

你的项目现在包含了两个模块,其目录结构如下:

|─ expenses.application

  1. |─ module-info.java
  2. |─ com
  3. |─ example
  4. |─ expenses
  5. |─ application
  6. |─ ExpensesApplication.java

|─ expenses.readers

  1. |─ module-info.java
  2. |─ com
  3. |─ example
  4. |─ expenses
  5. |─ readers
  6. |─ Reader.java
  7. |─ file
  8. |─ FileReader.java
  9. |─ http
  10. |─ HttpReader.java

14.5.2 requires子句

此外,你还可以像下面这样定义module-info.java

  1. module expenses.readers {
  2. requires java.base; ←---- 这是模块名,并非包名
  3. exports com.example.expenses.readers; (以下3行)这是包名,并非模块名
  4. exports com.example.expenses.readers.file;
  5. exports com.example.expenses.readers.http;
  6. }

这里新增的元素是requires子句,通过它你可以指定本模块对其他模块的依赖。默认情况下,所有的模块都依赖于名叫java.base的平台模块,它包含了Java主要的包,比如netioutil。默认情况下,这个模块总是需要的,因此你不需要显式声明(这个就跟Java语言中,class Foo { … }等价于class Foo extends Object { … }一样)。

如果你需要导入java.base之外的其他模块,requires子句就必不可少了。

requiresexports子句的组合使得Java 9中的访问控制变得更复杂了。表14-2总结了Java 9之前与之后使用不同的访问修饰符时在对象可见性上的差异。

表 14-2 Java 9在类的可见性上提供了更细粒度的控制

类的可见性 Java 9之前 Java 9之后
任何人都可以访问所有的类 ✓✓ ✓✓(结合exportsrequires子句)
有限的类可以公有访问 ✗✗ ✓✓(结合exportsrequires子句)
仅在模块内部可以公有访问 ✗✗ ✓ (不需要exports子句)
受保护的 ✓✓ ✓✓
包内可见 ✓✓ ✓✓
私有的 ✓✓ ✓✓

14.5.3 命名

现在是时候讨论如何命名模块了。我们会以一个比较短的名字为例进行介绍(譬如expenses.application),这样做主要是为了避免混淆模块与包的命名(一个模块可以导出多个包)。不过,对于模块和包的命名,推荐的命名规范是不一样的。

Oracle公司推荐大家在命名模块时采用与包同样的方式,即互联网域名规范的逆序(譬如,com.iteratrlearning.training)。此外,模块名应该与它导出的主要API的包名保持一致,包名也应该遵循同样的规则。如果模块中并不存在这样的包,或者出于某些别的原因模块的命名不能直接与它导出的包对应,那么这种情况下模块名也应以互联网域名规范同样的逆序方式设计,并在其中插入作者的名字。

现在你已经了解了如何构建一个多模块的项目,不过该怎样打包并运行它呢?别急,这些内容会在下一节中介绍。