Java 模块系统
Java 模块系统概述
Java 模块系统(Java Platform Module System,简称 JPMS)是 Java 9(2017 年发布)引入的一项重大特性,也被称为 Project Jigsaw。该系统旨在解决 Java 早期版本中类路径(classpath)管理的痛点,如 JAR Hell(依赖冲突)、模块化不足和封装性弱等问题。通过模块化,Java 应用可以更好地组织代码、提高安全性、减少依赖并提升性能。下面我将详细解释 Java 模块的核心概念、语法、用法以及优势与注意事项。
1. 什么是 Java 模块?
- 基本定义:模块(Module)是 Java 中一种新的代码组织单位,它是一组相关包(packages)的集合,加上一个模块描述文件(module-info.java)。模块可以明确定义其依赖的其他模块、导出的包(exposed packages),以及对反射的访问权限。
- 与包(Package)的区别:包是命名空间,用于组织类;模块是包的更高层抽象,用于组织应用或库的整体结构。模块可以包含多个包,但一个包只能属于一个模块。
- 模块类型:
- 命名模块(Named Module):有明确名称的模块,通常由开发者定义。
- 自动模块(Automatic Module):将旧的 JAR 文件作为模块使用时自动生成的模块,名称基于 JAR 文件名(去掉版本号和扩展名)。
- 未命名模块(Unnamed Module):所有类路径上的类默认属于未命名模块,不能被其他模块依赖。
- 模块路径(Module Path):类似于类路径,但专用于模块化的 JAR 文件。编译和运行时使用
--module-path或-p选项指定。
2. 如何定义一个模块?
模块的核心是 module-info.java 文件,它位于模块的根目录下,必须是源代码的一部分。以下是其基本语法:
module module.name { // 模块名称,通常是小写字母和点分隔,如 com.example.mymodule
// 依赖声明
requires other.module; // 依赖其他模块(transitive 表示传递依赖)
// 导出包
exports com.example.package; // 导出包给所有模块使用
exports com.example.internal to friend.module; // 限定导出,只给特定模块使用
// 打开包(用于反射)
opens com.example.reflection; // 允许运行时反射访问
// 服务提供与使用
provides com.example.Service with com.example.Impl; // 提供服务实现
uses com.example.Service; // 使用服务接口
// 其他(可选)
requires static another.module; // 编译时依赖,运行时可选
}- 关键指令:
requires:声明依赖的模块。如果加上transitive,则依赖是传递的(被依赖的模块也会被下游模块看到)。exports:导出包中的公共类型(public classes/interfaces),允许其他模块访问。未导出的包是内部的,无法从外部访问。opens:允许反射访问包中的类型(即使是非公共的)。在 Java 9+ 中,反射默认受限,需要显式打开。provides ... with:注册服务实现(Service Provider Interface, SPI)。uses:声明使用某个服务接口。
- 模块名称规则:建议使用反向域名(如 com.example.mymodule),避免冲突。
3. 模块的使用流程
- 创建模块:
- 在项目中创建
src/module.name目录结构。 - 在根目录下添加
module-info.java。 - 编写代码,确保包结构匹配导出声明。
- 在项目中创建
- 编译模块:
使用javac命令:
这会生成模块化的 .class 文件。javac -d mods/module.name --module-source-path src $(find src -name "*.java") - 打包模块:
使用jar命令创建模块化 JAR(Modular JAR):jar --create --file mlib/module.name.jar --module-version 1.0 -C mods/module.name . - 运行模块:
使用java命令指定主模块:java --module-path mlib -m module.name/com.example.MainClass - 多模块项目:在 Maven 或 Gradle 中支持模块化。例如,在 Maven 的 pom.xml 中添加
<module>module.name</module>,并使用 multi-module 项目结构。
4. 模块的优势
使用表格总结模块系统的关键优势与传统 classpath 的比较:
| 方面 | 传统 Classpath | 模块系统 (JPMS) |
|---|---|---|
| 封装性 | 弱,所有 public 类型全局可见 | 强,只有导出的包可见 |
| 依赖管理 | 容易冲突(JAR Hell) | 显式依赖,减少冲突 |
| 安全性 | 反射无限制 | 默认限制反射,需显式 opens |
| 性能 | 启动慢,加载所有类 | 更小镜像(jlink),更快启动 |
| 可维护性 | 代码组织松散 | 模块边界清晰,便于大型项目 |
| 兼容性 | 旧代码直接兼容 | 支持自动模块过渡旧 JAR |
- jlink 工具:用于创建自定义运行时镜像,只包含所需模块,显著减小 JDK 体积(从数百 MB 到数十 MB)。
- 服务加载:模块化增强了 ServiceLoader,允许更精确的服务发现。
5. 常见问题与注意事项
- 兼容旧代码:Java 9+ 兼容旧 JAR,但推荐逐步迁移。使用
--add-modules添加模块,或--add-exports临时导出包。 - 反射限制:许多库(如 Spring、Hibernate)依赖反射,在模块中使用时需添加
--add-opens或在 module-info 中 opens 包。 - 版本问题:模块不支持版本化依赖(不像 Maven),依赖名称唯一。
- 分模块 JDK:JDK 本身被拆分成模块(如 java.base、java.sql),应用只需 requires 所需模块。
- 潜在挑战:
- 迁移成本高:大型遗留项目需重构 module-info。
- 第三方库兼容:不是所有库都模块化,使用自动模块作为桥接。
- 测试:使用 JUnit 时,确保测试模块 requires junit。
- 最新发展(截至 2025 年):Java 模块系统在 Java 21+ 中进一步优化,如支持更细粒度的反射控制和更好的工具集成(例如 jpackage 用于打包模块化应用)。在云原生环境中,模块化有助于创建更小的 Docker 镜像。
6. 示例:一个简单的模块化应用
假设有两个模块:com.example.logger(提供日志服务)和 com.example.app(主应用)。
com.example.logger/module-info.java:module com.example.logger { exports com.example.logger; }com.example.app/module-info.java:module com.example.app { requires com.example.logger; }主类
com.example.app.Main:package com.example.app; import com.example.logger.Logger; public class Main { public static void main(String[] args) { Logger.log("Hello, Modules!"); } }
编译、打包后运行,即可看到模块化效果。

