第一部分
1. 引言
Java 9引入了一个新的特性,叫做Java模块系统(Java Platform Module System, JPMS)。这是Java在过去20多年中最大的一次改变。模块化的目标是提供更好的封装机制,使开发者能够创建更加稳定、安全和可维护的应用程序。本文旨在帮助你理解和应用JPMS。
2. 什么是模块?
在许多语言和系统中,模块是组织代码和数据的基本单元。在Java 9之前,Java的最小组织单元是包。但包存在一个问题:它们不能完全封装其内部实现。例如,如果一个包中有多个public类,任何其他的代码都可以访问它们,即使某些类是为内部使用而设计的。
JPMS的引入,使得Java开发者可以在更高的层次上组织代码,这被称为“模块”。一个模块可以包含多个包,并且明确声明它对外提供哪些API以及它依赖哪些其他模块。
3. 为什么需要模块化?
以下是模块化的主要好处:
- 更好的封装:你可以选择公开哪些包,而隐藏内部实现。
- 清晰的依赖关系:可以明确指定模块之间的依赖关系。
- 提高安全性:通过减少不必要的公开API,降低安全风险。
- 更小的运行时镜像:只包括必要的模块,创建更小的运行时环境。
4. JPMS的基本概念
模块描述文件是module-info.java
,它定义了模块的名称、导出的包和模块的依赖关系。
module com.example.myModule {
requires another.module;
exports com.example.myModule.api;
}
上面的代码声明了一个名为com.example.myModule
的模块,它依赖于another.module
并导出com.example.myModule.api
包。
5. 创建第一个模块
假设我们有一个简单的应用程序,它有两个模块:app
和library
。
5.1 library模块
目录结构:
library
│
└───src
│ module-info.java
│
└───com
└───example
└───library
LibraryClass.java
module-info.java:
module com.example.library {
exports com.example.library;
}
LibraryClass.java:
package com.example.library;
public class LibraryClass {
public String getMessage() {
return "Hello from the Library!";
}
}
5.2 app模块
目录结构:
app
│
└───src
│ module-info.java
│
└───com
└───example
└───app
App.java
module-info.java:
module com.example.app {
requires com.example.library;
}
App.java:
package com.example.app;
import com.example.library.LibraryClass;
public class App {
public static void main(String[] args) {
LibraryClass lib = new LibraryClass();
System.out.println(lib.getMessage());
}
}
现在,app
模块依赖于library
模块,并使用LibraryClass
来打印一条消息。
至此,我们已经介绍了Java模块系统的基本概念和如何创建模块。在下一部分,我们将深入探讨模块之间的互动和高级特性。
6. 模块间的交互
我们已经看到了如何创建模块和基本的依赖关系。现在,我们将更深入地探讨模块之间的互动。
6.1 模块的可见性
默认情况下,模块内的所有包都是不可见的,除非你明确地在module-info.java
中导出它们。只有被导出的包才能被其他模块访问。
例如,如果library
模块有一个internal
包,它不在module-info.java
中被导出,那么其他模块无法访问此包的公开类。
6.2 transitive依赖
你可能已经注意到,有时一个模块A依赖于模块B,而模块B又依赖于模块C。在这种情况下,为了使模块A的代码工作,模块A可能需要间接访问模块C的代码。
为了简化这种情况,JPMS引入了requires transitive
指令。
例如:
module com.example.moduleB {
requires transitive com.example.moduleC;
exports com.example.moduleB.api;
}
任何依赖moduleB
的模块也会自动获得对moduleC
的访问权限。
6.3 open模块和包
有时,你可能希望允许其他模块在运行时使用反射来访问你的模块的内部类。默认情况下,这是不允许的,但你可以使用open
指令打开整个模块或特定的包。
open module com.example.myOpenModule {
exports com.example.myOpenModule.api;
}
或者,只打开一个特定的包:
module com.example.myModule {
exports com.example.myModule.api;
opens com.example.myModule.internal;
}
7. 使用jlink创建自定义运行时镜像
Java 9引入了一个新工具jlink
,它允许你创建一个只包含你需要的模块的自定义Java运行时镜像。这可以大大减少部署的应用程序的大小。
例如,为上面的app
模块创建一个自定义的运行时镜像:
jlink --module-path /path/to/compiled/modules:$(JAVA_HOME)/jmods \
--add-modules com.example.app \
--output /path/to/output/directory
这将创建一个只包含app
模块和它的依赖的运行时镜像。
8. 非模块化的代码与JPMS
尽管JPMS为Java代码引入了新的模块化结构,但旧的Java代码仍然可以与模块化的代码共存。非模块化的代码被视为一个名为unnamed module
的特殊模块。
8.1 自动模块
如果一个JAR文件在模块化的环境中使用但没有module-info.java
,它会被自动转换为一个模块,其中所有包都被导出和打开。JAR文件的名称(去掉.jar
后缀)通常用作模块名称,但这不是强制的。
8.2 使用--add-exports
和--add-opens
如果你需要从命名模块访问未被导出或未被打开的包,你可以在运行时使用--add-exports
或--add-opens
选项来临时允许访问。
例如:
java --add-exports someModule/somePackage=com.example.myModule \
--add-opens anotherModule/anotherPackage=com.example.myModule \
-m com.example.myModule/com.example.myModule.Main
9. 模块版本
与许多依赖管理系统不同,JPMS不支持模块版本。原因是JPMS的设计目标是提供一种模块化机制,而不是成为一个依赖管理系统。在实际应用中,依赖管理通常由构建工具和包管理系统(如Maven或Gradle)处理。
10. 服务加载
JPMS提供了一种声明和使用服务的机制,使得模块化代码可以保持解耦。一个模块可以声明它提供的服务实现,而另一个模块可以声明它使用的服务。
10.1 声明和提供服务
一个模块可以通过provides
指令声明它为某个服务提供实现:
module com.example.serviceImpl {
requires com.example.serviceAPI;
provides com.example.serviceAPI.MyService with com.example.serviceImpl.MyServiceImpl;
}
在上面的例子中,MyServiceImpl
是MyService
接口的一个实现。
10.2 使用服务
要使用服务,模块需要声明它需要哪些服务,并使用ServiceLoader
来加载它们:
module com.example.app {
requires com.example.serviceAPI;
uses com.example.serviceAPI.MyService;
}
// 在代码中
ServiceLoader<com.example.serviceAPI.MyService> loader = ServiceLoader.load(com.example.serviceAPI.MyService.class);
for (MyService service : loader) {
// 使用服务
}
11. 最佳实践
11.1 封装内部实现
一般来说,应该只导出那些你想要其他模块使用的API,而尽量隐藏内部实现。这样,你就可以自由地更改内部实现,而不必担心破坏依赖于你的模块的代码。
11.2 避免循环依赖
模块之间不应该存在循环依赖,这是JPMS的一个基本规则。循环依赖通常是设计上的问题,并可能导致代码更难维护。
11.3 渐进的模块化
如果你有一个大型的、非模块化的代码库,尝试将整个代码库一次性转换为模块化可能是一项艰巨的任务。一个更好的方法是逐步、渐进地进行模块化。可以先从库或核心组件开始,然后逐渐向外部扩展。
12. 结论
Java平台模块系统(JPMS)为Java开发者提供了一个强大的工具,以更好地组织和封装代码。通过正确地使用JPMS,开发者可以创建更稳定、更安全和更可维护的应用程序。尽管初次接触可能会感觉有些复杂,但随着经验的积累,你会发现模块化为你的应用程序带来的好处。