
module-info.java 文件必须放在模块根目录
Java 模块系统不会自动发现或扫描子包里的 module-info.java,它只认顶层源码根目录(即 src/main/java 或 src 下直接放的)下的那个文件。放错位置会导致编译器完全无视模块声明,还可能报 error: module not found 或静默退化为非模块化运行。
- 正确路径:
src/main/java/module-info.java - 错误路径:
src/main/java/com/example/module-info.java(会被当成普通类) - IDE 有时会自动生成到错误位置,尤其从老项目升级时,务必手动检查并剪切到根目录
- Maven 构建时若用
maven-compiler-plugin,需显式设<release>9</release>或更高,否则module-info.java可能被跳过
requires 和 requires static 的区别不是“用不用”,而是“编译期依赖是否传递”
requires 声明的是运行时必需的模块;requires static 表示该模块仅在编译期需要(比如注解处理器、Lombok),不参与运行时链接。混淆这两者,轻则启动失败,重则模块图解析冲突。
- 用
@NonNull(来自javax.annotation)做空检查?→requires static java.annotation - 用
HttpClient发请求?→requires java.net.http(JDK 11+) - 如果误写成
requires static java.net.http,运行时会抛NoClassDefFoundError - 模块图验证(
java --list-modules或jdeps --module-path)能帮你确认实际依赖链是否符合预期
exports 和 opens 的边界控制很严格,反射和序列化容易崩
exports 控制的是 public 类能否被其他模块访问;opens 才允许反射访问(包括字段、方法、构造器),且只对指定包生效。没加 opens 却用 ObjectMapper 或 Spring 注入私有字段?直接 IllegalAccessException。
- 想让别的模块 new 你的
public class Config?→exports com.example.config - 还想让 Jackson 反序列化它的 private 字段?→
opens com.example.config(不能只靠 exports) - 如果只
opens不exports,外部模块连类名都看不到,反射也无从谈起 - JDK 9+ 的
--add-opens是临时绕过手段,上线前必须补上opens声明,否则模块封装形同虚设
模块路径(--module-path)和类路径(-cp)不能混用
一旦用了 --module-path,JVM 就进入“模块模式”,此时 -cp 上的 JAR 默认变成一个匿名模块(unnamed module),它能读所有命名模块,但反过来——命名模块默认无法读取匿名模块里的类。常见症状:明明 requires 写了,却提示 package not visible。
立即学习“Java免费学习笔记(深入)”;
- 启动命令必须统一:要么全走模块路径(
java --module-path mods -m my.module/com.example.Main),要么退回传统 classpath - 第三方库(如 Log4j、Guava)若未模块化,可打包进自己的模块(
Automatic-Module-NameMANIFEST 属性可辅助识别模块名),或用--add-modules显式引入 - Maven 中
maven-jar-plugin要配<archive><manifestEntries><Automatic-Module-Name>com.example.lib</Automatic-Module-Name></manifestEntries></archive>,否则 jar 名含数字/下划线会生成非法模块名
模块系统的真正复杂点不在语法,而在“可见性是分层叠加的”:exports 控制编译期引用,opens 控制运行时反射,requires 控制链接时可达性,而 --module-path 则决定了整个模块图的起点。漏掉任意一层,表现都可能是同一个错误——类找不到,但原因完全不同。










