requires是JPMS中声明模块依赖的显式契约,必须精准对应实际类型引用:仅导入真正使用的模块(如com.fasterxml.jackson.databind),核心模块java.base可省略但显式声明更清晰;编译期可选依赖用requires static;非模块化JAR需通过--add-reads隔离,JDK内部API限制须用--add-exports/--add-opens在启动时处理,不可写入module-info.java。

在 Java 模块系统(JPMS)中,requires 是声明模块依赖的基石。它不是“加个依赖就行”的简单操作,而是必须与运行时模块图严格对齐的显式契约。生产环境下的类路径冲突,往往源于 requires 声明不精准、混合路径未隔离、或未适配 JDK 内部封装收紧——这些都不是编译期警告,而是启动失败或运行时 IllegalAccessError。
精准声明 requires:只导出真正需要的模块
每个 requires 行都应对应当前模块代码中实际引用的类型。避免“为省事而全量 require”:
- 核心模块如
java.base可省略(JVM 自动读取),但显式写出更清晰、可读性强 - 第三方库必须声明完整模块名,例如 Jackson 2.15+ 默认提供
module-info.class,应写为requires com.fasterxml.jackson.databind;,而非旧版 JAR 的requires jackson.databind; - 若某依赖仅用于编译(如注解处理器、Lombok 编译期插件),用
requires static,确保运行时不强依赖该模块
切断类路径污染:识别并隔离未命名模块
当项目中混入非模块化 JAR(如老版本 Spring 或无 module-info.class 的工具包),它们会落入“未命名模块(unnamed module)”,而模块路径上的模块默认无法读取它——这是生产环境 NoClassDefFoundError 的常见根源。
验证方式:启动时加 --show-module-resolution,观察日志中是否有 unnamed module 被强制读取的警告。
解决策略:
- 优先升级依赖,选用已模块化的版本(查 Maven Central 的 “Module” 标签或 JAR 内是否存在
module-info.class) - 确需保留非模块化 JAR 时,用
--add-reads显式授权:java --module-path mods --add-reads com.example.service=ALL-UNNAMED -m com.example.service/com.example.Main - 禁止将模块化和非模块化 JAR 混放在同一路径下;模块路径(
--module-path)只放 .jmod 或含module-info.class的 JAR;类路径(-cp)仅用于遗留组件
应对 JDK 封装收紧:用 add-exports / add-opens 替代反射硬编码
Java 25 进一步限制对 sun.*、jdk.internal.* 等内部 API 的访问。若业务代码或中间件(如某些序列化框架)仍依赖 Unsafe 或 UnsafeFieldAccessor,仅靠 requires 无法解决。
正确做法是启动参数开放,而非修改模块声明:
-
--add-exports java.base/sun.nio.ch=ALL-UNNAMED:导出包供未命名模块使用 -
--add-opens java.base/java.lang=ALL-UNNAMED:开放包用于反射(如Field.setAccessible(true)) - 若目标模块已命名(如
com.example.service),把ALL-UNNAMED换成具体模块名,提升安全性
注意:这些参数不能写在 module-info.java 中,属于 JVM 启动时的运行时策略。
验证与调试:从模块图出发定位真实问题
不要凭经验猜冲突源。使用 JDK 自带工具生成模块图,确认依赖是否被正确解析:
- 编译后执行:
java --module-path out --list-modules,查看所有已加载模块列表 - 启动主类时加:
--show-module-resolution,输出每个模块如何被读取、谁触发了依赖 - 发现 “split package” 错误?说明两个不同模块导出了同名包(如
org.slf4j出现在 logback 和 slf4j-api 中),必须统一为同一模块版本,或用--patch-module合并

















