Java模块化系统JPMS入门
Java 9 引入了 Java Platform Module System(JPMS),这是 Java 语言发展史上的重大变革。本文介绍模块化的核心概念和基本用法。
为什么需要模块化
- 更小的运行时:只打包需要的模块,构建精简 JRE
- 强封装性:明确控制哪些包可以对外暴露
- 可靠的配置:编译期检查模块依赖
- 改进安全性:内部实现细节不再暴露
模块基础
module-info.java
每个模块根目录下需要声明模块描述符:
module com.example.app { requires java.base; requires java.sql; requires com.example.lib; exports com.example.app.api; opens com.example.app.entity; provides com.example.spi.Logger with com.example.app.FileLogger; uses com.example.spi.Logger; }
|
核心指令
| 指令 |
说明 |
| requires |
声明依赖模块 |
| requires transitive |
传递依赖 |
| exports |
导出包 |
| exports to |
定向导出 |
| opens |
开放包(反射) |
| opens to |
定向开放 |
| provides with |
提供服务实现 |
| uses |
消费服务 |
模块依赖示例
库模块
module com.example.lib { exports com.example.lib.api; exports com.example.lib.dto; opens com.example.lib.entity; requires static java.compiler; }
|
应用模块
module com.example.app { requires com.example.lib; requires java.logging; exports com.example.app.controller; opens com.example.app to spring.core; }
|
编译与运行
编译模块
javac -d out/lib $(find lib -name "*.java")
javac --module-path out/lib -d out/app $(find app -name "*.java")
|
运行模块
java --module-path out/lib:out/app -m com.example.app/com.example.app.Main
java --list-modules
java --describe-module java.base
|
打包 JMOD
jar --create --file out/app.jar --main-class com.example.app.Main -C out/app .
jmod create --class-path out/app out/app.jmod
|
自定义运行时镜像
使用 jlink 创建精简的 JRE:
jlink --module-path $JAVA_HOME/jmods:out/app \ --add-modules com.example.app \ --launcher app=com.example.app/com.example.app.Main \ --output myapp-runtime
./myapp-runtime/bin/app
|
效果:运行时镜像仅包含必要的模块,体积可从 200MB+ 减少到 50MB 以下。
未命名模块与自动模块
未命名模块(Unnamed Module)
类路径(classpath)上的所有类都属于未命名模块:
- 可以读取所有模块
- 所有模块可以读取它
- 用于兼容性过渡
自动模块(Automatic Module)
将普通 JAR 放在模块路径上,自动成为模块:
- 模块名从 JAR 文件名或
Automatic-Module-Name 属性推断
- 导出所有包
- 依赖所有模块
java --module-path libs:out/app --class-path legacy-lib.jar -m com.example.app
|
迁移策略
自下而上迁移
- 从最底层的库开始模块化
- 逐步向上迁移依赖项目
- 顶层应用最后迁移
关键步骤
jdeps --recursive --class-path libs app.jar
jdeps --generate-module-info out app.jar
|
与现有框架集成
Spring Boot
Spring 5+ 支持 JPMS:
module com.example.demo { requires spring.web; requires spring.boot; requires spring.boot.autoconfigure; opens com.example.demo to spring.core, spring.beans, spring.context; }
|
注意事项
- 反射访问需要 opens:Hibernate、Jackson 等框架需要反射,需要开放相应包
- 资源加载方式变化:使用
Module.getResourceAsStream()
- SPI 变化:推荐使用
provides/uses 替代 META-INF/services
总结
| 场景 |
建议 |
| 新建项目 |
直接使用模块化 |
| 类库开发 |
添加 module-info.java,支持模块化用户 |
| 遗留项目 |
先保证在模块路径上可用(自动模块) |
| 云原生部署 |
使用 jlink 构建精简运行时 |
模块化是 Java 平台现代化的重要一步,虽然迁移有成本,但长期收益显著:更小的部署包、更清晰的依赖关系、更好的封装性。