⭐ 推荐:社区规则条款 V1.0

Java 的模块化?

周末
周末 萌新站长
发布于 2026-10-20 22:22 ·13 浏览 ·0 回复
内容摘要

Java 9 引入的 JPMS 通过 module-info.java 在启动时校验依赖并实现强封装,但因其生态与框架适配不足、收益有限,普通业务应用应继续使用 classpath,仅公共库发布和极小运行时镜像场景值得采用。

Java 模块化(JPMS,Java Platform Module System,即 JSR 376)是 Java 9 在 2017 年 9 月引入的官方模块系统。它真正解决的是JDK 自身的拆分和库作者对强封装的需求;对大多数业务应用来说,上模块化弊大于利,除非你确实要裁剪运行时镜像,或者要发布公共库。

Java 模块化到底是什么?

结论:模块化的核心是一个叫 module-info.java 的声明文件,它把「依赖什么、对外暴露什么、允许谁反射进来」从约定写成了可以被编译器校验的契约。

JDK 9 之前,依赖关系全部写在 classpath 上,JVM 只有在运行到某一行代码、真的找不到类时才报 NoClassDefFoundError。模块化把这件事提前到了启动时:缺依赖直接启动失败,错误信息里会写明哪个模块 requires 了谁。

一个最小的 module-info.java 长这样:

module com.example.app {
    requires java.sql;                        // 依赖
    requires transitive com.example.core;     // 依赖并传递给下游
    exports com.example.app.api;              // 对外暴露的包
    opens com.example.app.model to com.fasterxml.jackson.databind; // 只对该模块开放反射
}

关键点:没写进 exports 的包,其他模块即使拿到 jar 也访问不到,编译期就报错。这才是「强封装」的实际含义。

普通业务项目需要做模块化吗?

结论:不需要。绝大多数 Spring Boot / 传统 Web 项目应该继续用 classpath,强行模块化只会带来一堆 --add-opens 和拆分包错误。

原因有三条:

  1. 生态不配合。大量流行库至今没有提供 module-info.class,放在模块路径上只能变成「自动模块」(automatic module,无 module-info 的 jar,会读取所有模块并导出所有包),强封装优势直接归零。
  2. 框架不配合。Spring Boot 的可执行 jar 采用自有分层结构,启动方式是 classpath 模式,官方从没把 JPMS 当作应用打包方案。
  3. 收益不匹配。模块化的收益是启动期依赖校验、更强的封装、可裁剪的运行时,业务系统的痛点通常是启动速度和内存,而这两点模块化本身解决不了。

真正值得做模块化的场景只有两个:发布给外部使用的公共库(让调用方获得可靠的依赖约束),以及需要极小运行时镜像的边缘/容器部署。

模块化和 classpath 到底有什么区别?

结论:classpath 是「一堆 jar 的平铺搜索路径」,模块路径是「有名字、有边界、有依赖声明的一张图」。

具体差异:

  • 可靠性:classpath 下两个 jar 里有同名类,谁生效取决于顺序;模块路径下同一个包被两个模块导出会直接报 module X reads package p from both A and B(拆分包错误,split package)。
  • 封装性:classpath 下所有 public 类都能被访问;模块下只有 exports 的包可见。
  • 反射:classpath 下 setAccessible(true) 基本畅通;模块下未 opens 的包会抛 InaccessibleObjectException。

怎么从零开始做模块化?

结论:先用 JDK 自带的 jdeps 分析依赖图,再手写 module-info.java,最后用 jlink 裁剪镜像,三步走。

第一步,看现有的 JDK 模块和内部 API 使用情况:

java --list-modules                          # 列出当前 JDK 的平台模块
jdeps --jdk-internals myapp.jar              # 找出对 JDK 内部 API 的依赖
jdeps --generate-module-info out --module-path libs myapp.jar  # 生成 module-info 草稿

第二步,把 module-info.java 放到源码根目录(Maven 项目里是 src/main/java/module-info.java),maven-compiler-plugin 从 3.6 起支持编译它,注意 release 要设成 9 以上。

第三步,需要裁剪运行时的话:

jlink --add-modules java.base,java.sql \
      --output myjre --strip-debug --compress=2

注意 jlink 只能处理显式模块(有 module-info.class 的模块)和 JDK 平台模块,自动模块不能进镜像——这是很多人第一次用 jlink 失败的原因。另外 JDK 21 起 --compress=2 已废弃,要改成 --compress=zip-6 这种形式。

模块化最常见的坑有哪些?

结论:三个坑占了实际问题的九成——JDK 内部 API 被强封装、自动模块命名混乱、拆分包无法解决。

坑一:强封装。JDK 16(2021 年 3 月,JEP 396)把 --illegal-access 的默认值改成 deny,JDK 17(2021 年 9 月,JEP 403)彻底移除该参数,反射访问 java.base 内部包必须显式加:

--add-opens java.base/java.lang=ALL-UNNAMED

坑二:自动模块名不稳定。没有 module-info.class 的 jar 在模块路径上,模块名由 META-INF/MANIFEST.MF 的 Automatic-Module-Name 决定;没写这个字段的话,名字从 jar 文件名推导(去掉版本号、非字母数字转成点),一旦上游改了版本号格式,你的 requires 就可能编译失败。结论:写库的人务必在 Manifest 里加 Automatic-Module-Name。

坑三:拆分包。如果两个 jar 里都有 com.example.util 这个包,在模块路径上无解,只能合并或换包名——这就是 javax.annotation、javax.xml.bind 这类包在 Java 11 之后必须整体外迁的原因。

总结

Java 模块化是 JDK 9 交付的一套「依赖声明 + 强封装 + 可裁剪运行时」机制,module-info.java 是它的入口。它成功地改造了 JDK 自身(java.base 等平台模块),但对业务应用来说,自动模块泛滥、框架不兼容、拆分包无解这三点决定了它至今不是默认选择。判断标准很简单:你要发布公共库,或者要用 jlink 把运行时压到 50MB 以下,就做;否则继续用 classpath,把精力放在别处。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2330.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~