module-info.java必须置于src/main/java根目录,与包目录平级;requires声明依赖、exports控制编译期类型可见性、opens专管反射权限、transitive解决传递性断裂。

直接写 module-info.java 文件本身不难,难的是写完编译不过、运行报错、或者别人根本用不了你的模块——核心问题从来不是语法,而是路径、依赖声明和导出边界没对齐。
module-info.java 必须放在 src/main/java 根目录
这是最常踩的坑:IDE(尤其是 IntelliJ)自动生成时可能把它塞进 src/main/java/com/example/ 里,结果编译器压根不认。
- 正确位置:
src/main/java/module-info.java(与任何com/或org/包目录平级) - 错误位置:
src/main/java/com/example/module-info.java→ 编译报error: module not found - Maven 项目还要检查
pom.xml是否设置了 Java 版本:(OpenJDK 17+ 推荐),否则17 module-info.java被忽略
requires 和 exports 是最基础的两个指令
requires 声明你“要用谁”,exports 声明你“让谁用”。两者缺一不可,且必须匹配消费方的实际调用路径。
- 比如你要用
java.logging,就得写requires java.logging;;不写,连import java.util.logging.Logger都编译失败 - 你想让别人调用
com.example.service.UserService,就得exports com.example.service;;只把类设为public没用,包没导出就是不可见 - 如果依赖的是第三方 JAR(如 Jackson),它得是命名模块(有
Automatic-Module-Name或自带module-info.class),否则只能当自动模块用,名字由 JAR 名推导(比如jackson-databind-2.15.2.jar→ 模块名jackson.databind)
opens 才管反射,exports 不管私有字段
JSON 反序列化、Spring Bean 注入、Hibernate 映射这些依赖反射的操作,exports 完全无效。必须用 opens,否则运行时报 IllegalAccessException。
- 开放整个包给所有模块:
opens com.example.model; - 更安全的做法是限定目标模块:
opens com.example.model to com.fasterxml.jackson.databind; - 注意:
opens和exports可以共存,但语义不同:前者只影响反射,后者只影响编译期类型引用
transitive 解决“用了别人的 API 却找不到实现类”的问题
你模块 A 依赖模块 B,B 内部用了 java.sql 并把 DataSource 类型暴露给了你,但你代码里直接 new 一个 DataSource 子类就报错?大概率是 B 没加 transitive。
- B 的
module-info.java写了requires java.sql;→ A 只能访问 B 的代码,不能直接用java.sql的类型 - B 改成
requires transitive java.sql;→ A 就能直接 importjava.sql.DataSource了 - 常见场景:工具模块(如 utils)封装了日志、配置、HTTP 客户端,下游业务模块要直接用这些三方类型时,必须靠
transitive透出
真正卡住人的往往不是语法记不住,而是模块路径放错、requires 拼错模块名(比如写成 javafx.base 而不是 javafx.base)、或误以为 exports 能解决反射问题。动手前先跑通一个空模块,再一条条加声明,比堆完再调试快得多。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











