Java模块系统:深入探索JPMS及其应用

第一部分

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. 创建第一个模块

假设我们有一个简单的应用程序,它有两个模块:applibrary

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;
}

在上面的例子中,MyServiceImplMyService接口的一个实现。

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,开发者可以创建更稳定、更安全和更可维护的应用程序。尽管初次接触可能会感觉有些复杂,但随着经验的积累,你会发现模块化为你的应用程序带来的好处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_57781768

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值