Skip to main content
 首页 » 编程设计

Java 模块化

2022年07月19日123softidea

Java 9 模块化

Java 9 引入模块(JPMS,Java Platform Module System), 其是在包上增加了新的抽象级别。本文主要介绍JPMS并讨论它的多个方面内容。

1. 模块概述

首先,在学习如何使用模块之前需先理解模块是什么?模块是一组紧密相关的包、资源以及模块描述文件。也就是说,它是"Java包的包"抽象,增强代码可重用性。

模块中的包与Java诞生以来一直使用的包是相同的。当我们创建模块时,我们在包中组织代码,就像以前对所做的方式一样。除了组织代码外,还使用包来确定哪些代码可以在模块外部公开访问,在本文后面将花更多篇幅讨论该问题。

每个模块负责其资源,如媒体或配置文件。之前资源放在项目根级别,我们手动管理资源属于不同应用的部分。
创建模块时,需要包括模块描述文件,其中定义几个方面的内容:

  • Name – 模块名称
    Dependencies – 该模块依赖其他模块列表
    Public Packages – 允许模块外能够访问的公共包列表
    Services Offered – 提供给被其他模块消费的服务实现
    Services Consumed – 允许当前模块成为一个服务消费者
    Reflection Permissions – 明确允许其他类使用反射访问包的私有成员

模块命名规则类似于包名称(可以使用点,不能使用破折号),通常也是项目命名凡是(my.module)或DNS的反向形式(com.baidu.mudule)。

我们需要列出所有想公开的包,默认情况下所有包是模块私有的。对于反射也是如此。默认情况下,我们不能对从另一个模块导入的类使用反射。在本文的后面,我们将查看如何使用模块描述符文件的示例。

  • 模块类型

新引入的模块系统有四种模块:

系统模块:运行list-modules命令返回的模块,包括java SE 和 JDK模块。

应用程序模块:当我们决定使用模块时,我们通常希望构建这些模块。一般在编译的module-info.class文件中命名、定义,包括在Jar文件中。

自动模块:通过增加已存在jar至模块路径引入非官方模块。模块的名称将从JAR的名称派生出来,自动模块将对路径加载的所有其他模块具有完全的读访问权。

非命名模块:当类或jar加载到类路径,不是模块路径,则会自动增加非命名模块。它是笼统全面的模块,用于维护与之前Java代码,保持向后兼容性。

  • 模块分发

模块可以通过两种方式分发:jar包形式或“暴露的”编译项目,当然该项目与其他Java项目一样,没有什么新奇。

我们可以创建有多个模块项目组成一个主应用和几个库模块。需要注意的是每个jar文件只能有一个模块,因此准备构建文件时,我们需要确保绑定项目中每个模块作为一个独立jar。

安装Java9 时能看到JDK有了新的结构,已经将所有的原始包转移到新的模块系统中。输入下面命令可以看到这些模块:

java --list-modules 

主要分为四组: java, javafx, jdk, Oracle。java模块是核心SE 语言规范实现类。javafx模块是FX UI库。jdk自身需要的在jdk模块中,最后Oracle规范相关的在oracle模块中。

2. 模块声明

创建模块需要在包的根路径下放一个特殊文件 module-info.java,该文件称为模块描述文件,包括所有必要的数据用于构建新模块。
使用声明构建模块,主体内容可以为空或有模块指令组成:

module myModuleName { 
    // all directives are optional 
} 

使用关键字module开始模块声明,后面跟上模块名称。这是模块已经正常工作,但一般需要更多信息,即模块指令。

2.1. Requires

第一个指令是 requires。用于声明模块依赖:

module my.module { 
    requires module.name; 
} 

现在my.module模块有一个运行时和编译时的依赖模块:module.name。通过requires指令使得依赖模块中所有公开暴露类型都可以被当前模块访问。

2.2. Requires Static

有时需要引用其他模块,但实际并不会使用。举例,可能需要写一个工具函数用于当日志模块存在时格式化输出内部状态,但并不是每个库使用者都需要该功能,他们并不想增加额外的日志模块。这时需要使用可选依赖指令。通过可选依赖指令,可实现仅在编译依赖:

module my.module { 
    requires static module.name; 
} 

2.3. Requires Transitive

通常使用库使得编码更加简单。但我们需啊哟确保任何引用的模块也同时引用其依赖的模块,否则不能正常工作。幸运的是我们能使用 requires transitive 指令强制任何下游消费者也读取必要的传递依赖:

module my.module { 
    requires transitive module.name; 
} 

现在当开发者引入 my.module模块时,无需再显示声明引入module.name模块。

2.4. Exports

缺省情况下,模块不暴露任何API给其他模块。这种强封装机制是最初创建模块系统的关键动机之一。主要我们代码显然更加安全,但现在需要显示开放API给其他模块。
可以使用exports指令暴露所有对应包下的public成员:

module my.module { 
    exports com.my.package.name; 
} 

现在当某人引入my.module模块时,则可以访问 com.my.package.name包下的所有public成员,但仅限于当前声明的包。

2.5. Exports … To

我们能适用export指令开放public类给其他模块,但有时不想让所有人都能访问我们的API。这时可以使用exports…to指令限制仅那个模块可以访问。
与exports指令类似,声明公开一个包,但也能列出允许哪些模块可以导入这个包。例如:

module my.module { 
    export com.my.package.name to com.specific.package; 
} 

2.6. Uses

服务(service)是可以被其他类消费特定接口或抽象类的实现,使用uses指令指定我们模块消费的服务。
注意class.name也可以是服务接口、抽象类的名称,不是实现类:

module my.module { 
    uses class.name; 
} 

这里应该注意requires和users指令的区别。

我们可能使用requires指令引入了提供服务的模块,但服务实现的接口来自某个传递依赖。这时无需强制我们模块通过requires引入所有传递依赖,可以使用uses指令引入接口至模块路径。

2.7. Provides … With

模块也作为其他模块消费的服务提供者(service provider)。首先使用provides关键字,然后是接口或抽象类名称,接着使用with指令指定实现类名称,即实现接口或继承抽象类的类名称。示例如下:

module my.module { 
    provides MyInterface with MyInterfaceImpl; 
} 

2.8. Open

前面提及设计模块系统的动机为封装。Java9之前,可以使用反射检查包中各种类型和成员,甚至是私有成员。这不是真正的封装,这为库开发者带来各种问题。Java9采用强制封装,因此必须显示授权给其他模块对我们的类进行反射。如果需要继续允许如Java老版本那样的所有反射功能,需要简单开放整个模块:

open module my.module { 
} 

2.8. Opens

如果允许反射私有类型,但不是暴露所有代码,可以使用opens指令直接暴露特定包。但注意,这样即开放给整个世界,所以需要确认:

module my.module { 
  opens com.my.package; 
} 

2.9. Opens … To

有时反射是有必要的,但仍需要如封装提供的安全性。我们可以有选择性开放包给特定模块,使用opens…to指令:

module my.module { 
    opens com.my.package to moduleOne, moduleTwo, etc.; 
} 

3. 可见性

这里花点时间讨论代码可见性。很多库依赖反射实现其魔法功能(JUnit和Spring),Java 9缺省情况下只能访问导出包的public类、方法和字段。即使使用反射访问非public成员并设置setAccessible(true),也不能成功访问这些成员。

我们可以使用open, opens, and opens…to指令授权运行时反射访问,注意这仅仅是在运行时。我们不能对私有类型进行编译,而且永远也不需要这样做。
如果我们必须要编译时访问私有类型,并且也不是模块的拥有者(即不能使用opens…to指令),那么可以使用命令行–add-opens 选项允许自己模块在运行时反射访问锁定的模块。

这里惟一需要注意的是需要访问命令行参数,这些参数用于运行模块以使其工作。

4. 总结

本文讨论了Java 9 模块系统的基本概念,模块是什么,模块文件声明,后续继续提供一些实战内容。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/104038399
阅读延展