一、Java Module System 简介
Java 平台模块化系统(Module System)是从 Java 9 开始引入的一项新特性。它允许将一个 Java 应用程序或库划分为多个模块,每个模块都是一个独立的单元,可以定义自己的接口和实现,同时明确规定与其他模块之间的依赖关系。这种方法具有诸多优点,能够更好地控制代码的可见性和访问权限,减少代码冲突和依赖问题,提高代码的可维护性、可测试性和可扩展性。
模块是一个可重用的、独立的、自包含的代码单元,由一组相关的类、接口、枚举和注解组成。模块可以用来封装代码、提供抽象接口和实现、控制访问权限和可见性,以及明确规定与其他模块之间的依赖关系。每个模块都必须包含一个模块描述文件(module-info.java),用于指定模块的名称、版本、依赖关系和导出的包。模块描述文件是一个 Java 源代码文件,可以使用 Java 编译器进行编译,并打包成一个 JAR 文件,可以在 Java 虚拟机中加载和执行。
例如,一个名为 “com.example.myapp” 的模块可以包含以下模块描述文件:
module com.example.myapp {
requires java.base;
requires com.example.mylib;
exports com.example.myapp.api;
exports com.example.myapp.impl to com.example.myapp.plugin;
}
上面的模块描述文件指定了该模块的名称为 “com.example.myapp”,它需要 Java 的基础模块(java.base)和自定义的 “com.example.mylib” 模块。同时,它将 “com.example.myapp.api” 包导出给其他模块使用,但只将 “com.example.myapp.impl” 导出给 “com.example.myapp.plugin” 使用。
二、Java Module System 是什么
(一)模块的定义
模块是一个可重用的、独立的、自包含的代码单元,由一组相关的类、接口、枚举和注解组成。每个模块都必须包含一个模块描述文件(module-info.java),用于指定模块的名称、版本、依赖关系和导出的包。
模块是 Java 9 引入的新特性,它提供了一种组织和部署 Java 代码的方法。模块类似于一个容器,可以将相关的类、资源等封装在一起,同时明确规定与其他模块之间的依赖关系。这种封装性使得代码更加清晰、易于维护,同时也提高了代码的安全性和可扩展性。
例如,一个名为 “com.example.myapp” 的模块可以包含以下模块描述文件:
module com.example.myapp {
requires java.base;
requires com.example.mylib;
exports com.example.myapp.api;
exports com.example.myapp.impl to com.example.myapp.plugin;
}
在这个模块描述文件中,“module com.example.myapp” 定义了模块的名称为 “com.example.myapp”。“requires java.base; requires com.example.mylib;” 声明了该模块依赖于 Java 的基础模块(java.base)和自定义的 “com.example.mylib” 模块。“exports com.example.myapp.api; exports com.example.myapp.impl to com.example.myapp.plugin;” 则表示将 “com.example.myapp.api” 包导出给其他模块使用,同时只将 “com.example.myapp.impl” 导出给 “com.example.myapp.plugin” 使用。
(二)设计背景及动机
- 解决传统 Java 开发中的技术痛点,如代码冲突、版本不兼容、难以控制的类路径等问题。
在传统的 Java 开发中,开发人员通常需要将所有的 Java 类和依赖库打包成一个 JAR 文件,并将这个 JAR 文件添加到 classpath 中。这种方式虽然简单易用,但是在复杂的应用程序中,很容易出现类冲突和版本不兼容问题。例如,当一个应用程序同时使用了多个依赖库,这些依赖库中可能会有相同的类名或者不同版本的类,这就会导致运行时的类冲突和异常。
为了解决这些问题,Java 平台模块化系统被设计出来了。它将一个 Java 应用程序或库划分为多个模块,每个模块都是一个独立的单元,可以定义自己的接口和实现,同时明确规定与其他模块之间的依赖关系。这种方式更加灵活,可以控制代码的可见性和访问权限,减少代码冲突和依赖问题,提高代码的可维护性、可测试性和可扩展性。
- 提供更好的封装、强制模块化、缩小 JDK 的体积、提高安全性以及支持 Java 平台的演进。
(1)更好的封装:在传统的 Java 中,包级别的访问控制机制不足以严格控制 API 的访问。Java Module System 允许模块间定义明确的导出和依赖关系,从而提供更强的封装和信息隐藏。
(2)强制模块化:通过 Java Module System,可以强制项目模块化,使得代码更容易维护、测试和重用。模块化可以提高开发效率和代码质量。
(3)缩小 JDK 的体积:JDK 9 中的模块化使得 JDK 本身可以被分解为多个模块。这样,开发者可以只包含他们需要的模块,而不是整个 JDK,从而减少应用程序的体积和启动时间。
(4)提高安全性:通过模块化,敏感的内部 API 可以被隐藏,只有明确导出的 API 才对外可见。这可以减少潜在的安全风险,因为内部实现细节不再暴露给外部代码。
(5)支持 Java 平台的演进:随着 Java 平台的发展,引入新的特性和 API 变得更加容易,因为模块化系统提供了更清晰的结构和依赖管理机制。
三、Java Module System 的适用范围
Java Module System 的适用范围非常广泛,适用于所有使用 JDK 开发的项目,涵盖了 Java SE、Java EE、Java ME 等各种 Java 平台的应用程序和库。
一些知名的开源项目已经采用了模块化系统,充分展示了其在实际开发中的价值。例如,Apache Maven 4.x 作为流行的 Java 构建工具,支持 Java 9 的模块化系统并计划在未来增加更多功能,这使得构建过程更加灵活和可管理。
Spring Framework 5.x 作为广泛应用的 Java 应用程序开发框架,也采用了模块化系统来组织代码。Spring 的核心容器由多个模块组成,如 spring-core、spring-beans、spring-context、spring-context-support 和 spring-expression 等。这些模块相互协作,为开发人员提供了强大的功能和灵活性。通过模块化系统,Spring Framework 能够更好地控制代码的可见性和访问权限,提高代码的可维护性和可扩展性。
Eclipse IDE 2021.x 作为流行的 Java IDE,使用模块化系统来组织插件和功能。这使得 Eclipse 的扩展和定制更加方便,开发人员可以根据自己的需求选择和加载特定的模块,提高开发效率。
JUnit 5.x 作为流行的 Java 测试框架,使用模块化系统来组织代码。这使得测试用例的编写和管理更加清晰,提高了测试的可维护性和可扩展性。
Guava 29.x 作为流行的 Java 库,提供了许多实用的工具类和函数,也采用了模块化系统来组织代码。这使得开发人员可以更加方便地使用 Guava 的功能,同时也提高了库的可维护性和可扩展性。
总之,Java Module System 为各种使用 JDK 开发的项目提供了一种更加灵活、可控制、可维护的代码组织方式,有助于提高开发效率和代码质量。
四、开发示例
1. 定义一个 module-info.java 文件来指定模块信息和依赖关系。
在 Java 的模块化系统中,模块描述文件module-info.java起着至关重要的作用。它允许开发者明确指定模块的名称、版本、依赖关系以及导出的包。例如,一个名为 “com.example.myapp” 的模块可以有如下的module-info.java文件:
module com.example.myapp {
requires java.base;
requires com.example.mylib;
exports com.example.myapp.api;
exports com.example.myapp.impl to com.example.myapp.plugin;
}
在这个文件中,“module com.example.myapp” 定义了模块的名称。“requires java.base; requires com.example.mylib;” 声明了该模块依赖于 Java 的基础模块(java.base)和自定义的 “com.example.mylib” 模块。“exports com.example.myapp.api; exports com.example.myapp.impl to com.example.myapp.plugin;” 则表示将 “com.example.myapp.api” 包导出给其他模块使用,同时只将 “com.example.myapp.impl” 导出给 “com.example.myapp.plugin” 使用。
2. 创建包含两个类的 Java 项目,分别在不同模块中,并为每个模块创建 module-info.java 文件,声明模块名称和依赖关系。
假设我们有一个应用程序,它有两个模块:一个模块用于处理数据库连接,另一个模块用于处理用户界面。
首先,创建数据库模块。数据库模块包含一个简单的类DatabaseConnection:
// DatabaseConnection.java
package com.example.database;
public class DatabaseConnection {
public void connect() {
System.out.println("Connected to the database.");
}
}
为该模块创建一个module-info.java文件,声明该模块的名称:
// module-info.java
module databaseModule {}
这个模块不依赖于其他模块,所以module-info.java文件中没有声明任何依赖关系。
接下来,创建用户界面模块。用户界面模块包含一个简单的类UserInterface:
// UserInterface.java
package com.example.ui;
public class UserInterface {
public void display() {
System.out.println("User interface displayed.");
}
}
为该模块创建一个module-info.java文件,声明该模块的名称和对数据库模块的依赖:
// module-info.java
module uiModule {
requires databaseModule;
}
这个模块声明了对databaseModule的依赖,这意味着它可以使用databaseModule中的类和资源。
3. 使用 Java 9 编译器编译模块,然后运行应用程序。
现在,我们可以使用 Java 9 编译器来编译这两个模块:
javac -d out/database databaseModule/*.java
javac -d out/ui --module-path out databaseModule/*.java uiModule/*.java
然后,我们可以运行UserInterface类来启动我们的应用程序:
java --module-path out -m uiModule/com.example.ui.UserInterface
这将会输出:
Connected to the database.
User interface displayed.
五、模块化编程的优势(Java)
- 强封装性:精准明确地指定模块的公开 API 以及内部实现,强化封装性。
在大型企业级应用中,例如跨国公司的管理系统,对于敏感的员工薪资数据存储模块,通过模块化可以将数据结构和算法严密封装,仅对外暴露必要接口,避免外部对内部实现的不必要访问,保障数据安全。
- 清晰的依赖管理:每个模块必须清晰明确地声明其依赖的其他模块,形成稳定的依赖管理机制。
以电商平台开发为例,订单模块依赖于用户模块和商品模块。通过明确声明依赖关系,在构建和运行时,相关模块能够准确无误地加载,杜绝因依赖不明确而引发的运行时错误,提升项目开发效率。
- 提高性能:助力 JVM 和编译器做出更优化的决策,减少资源消耗,提升应用响应速度。
在微服务和云原生应用场景中,模块系统优势显著。如微服务架构的在线教育平台,每个微服务作为独立模块,运行时只有被调用的微服务模块才会被加载,极大减少资源消耗,提升应用响应速度,为用户带来更流畅的体验。
- 更易于构建大型系统:鼓励将大型复杂程序拆分为更小、更易于管理的部分,提高代码重用性。
模块化系统使得大型系统的构建、测试和维护变得更加轻松。通过将大型复杂程序拆分为小模块,提高了代码的可管理性和可维护性,同时也提高了代码的重用性。
- 更好的安全性:限制模块之间的访问权限,隐藏敏感的内部 API。
模块化可以限制模块之间的访问权限,提供更好的封装性和安全性。只有导出的包才可以被其他模块访问,其他的包是不可见的,减少了潜在的安全风险。
- 减少应用体积:JDK 9 中的模块化使得 JDK 本身可以被分解为多个模块,开发者可以只包含需要的模块。
JDK 9 的模块化使得 JDK 可以被分解为多个模块,开发者可以根据实际需求只包含需要的模块,而不是整个 JDK,从而减少应用程序的体积和启动时间。
- 促进模块间的明确界限:每个模块都有清晰的功能边界,提高模块内部代码逻辑的紧密性。
模块化使得每个模块都有清晰的功能边界,模块内部代码逻辑更加紧密。这有助于提高代码的可维护性和可扩展性,同时也使得模块之间的交互更加清晰和可控。
六、大型项目中 Java Module System 的挑战
- 清晰的依赖管理可能在复杂项目中变得难以维护。
在大型项目中,随着模块数量的增加和模块之间依赖关系的复杂化,清晰的依赖管理可能会变得极具挑战性。尽管 Java Module System 通过模块描述文件(module-info.java)明确声明了模块的依赖关系,但在实际的大型项目中,由于业务需求的不断变化和项目的持续演进,模块之间的依赖关系可能会频繁变动。这就需要开发人员不断地更新模块描述文件,以确保依赖关系的准确性。然而,在大规模的项目中,手动维护这些依赖关系可能会变得非常繁琐,容易出现错误。例如,在一个拥有数百个模块的企业级应用中,当一个模块的功能发生变化,可能会影响到多个其他模块的依赖关系。这就需要开发人员仔细分析每个受影响的模块,更新其依赖关系声明,确保整个系统的正常运行。这种手动维护的方式不仅效率低下,而且容易出现遗漏或错误,导致系统在运行时出现各种问题。
- 模块之间的可见性控制可能导致开发过程中的复杂性增加。
Java Module System 通过 exports 和 requires 关键字严格控制了模块之间的可见性。虽然这提高了系统的安全性和封装性,但在开发过程中也可能带来一些复杂性。例如,当一个开发人员需要在一个模块中使用另一个模块的功能时,必须确保目标模块正确地导出了所需的包,并且当前模块正确地声明了对目标模块的依赖。如果开发人员不熟悉模块之间的可见性规则,可能会花费大量时间来调试为什么无法访问其他模块的功能。此外,在大型项目中,模块之间的可见性控制可能会导致模块之间的交互变得更加复杂。开发人员需要仔细考虑哪些包应该被导出,哪些模块应该被依赖,以避免出现不必要的依赖关系和潜在的安全问题。
- 自动模块可能带来一些不确定性和兼容性问题。
在 Java 9 中,位于类路径上的非模块化代码被称为 “自动模块”。自动模块默认是可见的,但只能访问其他模块公开的部分。虽然自动模块在一定程度上方便了从传统的 Java 项目向模块化项目的过渡,但也可能带来一些不确定性和兼容性问题。例如,自动模块的依赖关系可能不明确,因为它们没有像模块化模块那样通过 module-info.java 文件明确声明依赖关系。这可能导致在运行时出现意外的依赖错误。此外,自动模块可能与模块化模块之间存在兼容性问题,特别是当自动模块依赖于旧的库或框架时。开发人员需要花费额外的时间来解决这些兼容性问题,确保整个系统的稳定运行。
- 模块层次结构的构建和维护需要一定的技术水平和经验。
在大型项目中,构建和维护模块层次结构是一项具有挑战性的任务。模块层次结构的设计需要考虑到项目的业务需求、功能模块的划分以及模块之间的依赖关系。开发人员需要具备一定的技术水平和经验,才能设计出合理的模块层次结构。例如,在一个复杂的企业级应用中,开发人员可能需要根据不同的业务领域将项目划分为多个模块,然后再根据模块之间的依赖关系构建层次结构。这需要开发人员对项目的业务逻辑有深入的理解,同时还需要熟悉 Java Module System 的特性和用法。此外,随着项目的不断演进,模块层次结构也需要不断地调整和优化。这就要求开发人员能够及时发现模块层次结构中存在的问题,并采取有效的措施进行改进。否则,不合理的模块层次结构可能会导致系统的性能下降、可维护性降低等问题。
七、Java Module System 在大型项目中的应用案例
- 清晰的依赖管理使得大型项目的代码结构更加清晰,易于维护。
在大型项目中,Java Module System 的清晰依赖管理发挥着重要作用。例如在一个大型企业级应用中,可能涉及众多模块,每个模块都有明确的功能定位。通过模块描述文件(module-info.java),可以清晰地指定每个模块所依赖的其他模块。这种明确的依赖关系使得开发人员能够快速了解项目的整体结构,当需要进行代码维护时,可以更准确地定位问题所在。比如在一个金融系统项目中,交易模块依赖于账户管理模块和风险评估模块。通过 Java Module System,开发人员可以清楚地看到这种依赖关系,当交易模块出现问题时,可以首先检查与之相关的账户管理模块和风险评估模块是否正常工作,从而提高维护效率。
- 增强的封装性提高了大型项目的安全性,减少了内部实现细节的暴露。
对于大型项目而言,安全性至关重要。Java Module System 的增强封装性可以有效地提高项目的安全性。在大型项目中,不同的模块可能由不同的团队开发,通过 exports 和 requires 关键字,可以严格控制模块之间的可见性。只有明确导出的包才可以被其他模块访问,其他的包是不可见的,减少了潜在的安全风险。例如在一个医疗信息管理系统中,患者信息模块可以将敏感的患者数据进行严格封装,只对外暴露必要的接口,其他模块无法直接访问内部实现细节,从而保护患者隐私。
- 更好的性能使得大型项目的启动速度更快,资源消耗更少。
在大型项目中,性能是一个关键问题。Java Module System 可以提高大型项目的性能,使得启动速度更快,资源消耗更少。由于模块之间的依赖关系明确,JVM 可以仅加载运行应用程序所需的模块,减少了内存占用。例如在一个电商平台项目中,商品展示模块、订单处理模块和支付模块等可以根据用户的操作动态加载,当用户浏览商品时,只有商品展示模块和相关的模块被加载,其他模块暂不加载,从而提高启动速度,减少资源消耗。
- 通过模块的划分,可以更好地组织大型项目的代码,提高开发效率和可维护性。
大型项目通常具有复杂的业务逻辑和庞大的代码量,通过模块的划分,可以更好地组织代码。每个模块都可以独立开发、测试和维护,提高了开发效率。例如在一个物流管理系统中,可以将运输管理模块、仓储管理模块、订单管理模块等分别进行开发,开发人员可以专注于自己负责的模块,减少了代码之间的干扰。同时,当需要进行功能扩展或修改时,也可以更加方便地进行,提高了可维护性。例如,如果需要对运输管理
本文暂时没有评论,来添加一个吧(●'◡'●)