exports 控制编译期和运行期的类可见性,opens 仅控制运行期反射访问权限;未声明 opens 时,即使 exports 了包,反射访问私有成员仍抛 illegalaccessexception。

module-info.java 里 exports 和 opens 的区别到底在哪
exports 控制编译期和运行期的类可见性,opens 只控制运行期的反射访问权限。不加 opens 的模块,哪怕 exports 了包,setAccessible(true) 也会抛 IllegalAccessException。
常见错误现象:用 Spring、Hibernate 或 JUnit 5 测试带私有字段的类时突然报反射失败,但编译完全正常——大概率是漏写了 opens。
- 只读取 public 字段/方法 →
exports就够 - 需要反射操作 private 成员(比如框架注入、序列化)→ 必须配
opens -
opens不传递:A → requires B → B opens pkg,C requires B 但 C 无法通过 B 反射访问该 pkg - JDK 17+ 默认禁用非法反射警告,但
--illegal-access=deny下没opens就直接失败
为什么 package-private 类在跨模块调用时总报 NoClassDefFoundError
模块系统不会“穿透”包级访问控制。即使两个模块都导出同一包名,只要类是 package-private(无修饰符),另一个模块就看不到它——exports 只让 public 类可见,不提升访问级别。
使用场景:你想把工具类设为包内可见,又希望被其他模块安全复用,但又不想暴露 public API。
立即学习“Java免费学习笔记(深入)”;
- 方案一:把类改成
public,再靠exports精确控制范围 - 方案二:用
provides ... with+ service interface,把实现类保持 package-private,只暴露接口 - 别试图用
requires static绕过——它只影响编译依赖,不改变运行时访问规则 - IDE 可能不报错(因为编译器看到 exports 就放行),但 JVM 加载时才真正校验,容易漏测
javac 编译时提示 module not found,但 jar 包明明在 classpath 里
模块路径(--module-path)和类路径(-cp)互不兼容。一旦用了 --module-path,JVM 就进入模块模式,classpath 上的 jar 全部被忽略——包括你习惯放的 commons-lang3.jar 这类传统库。
错误典型表现:javac --module-path mods -d out Main.java 编译失败,但删掉 --module-path 就行;或者运行时报 java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils。
- 传统 jar 没
module-info.class→ 自动归入unnamed module,必须显式requires它(且名字是 jar 文件名转成点分格式,如requires commons.lang3;) - 更稳妥做法:用
jdeps --list-deps查依赖图,再补requires - Maven 用户注意:
maven-compiler-plugin3.8+ 才支持<modulepath></modulepath>配置,旧版会静默失效
如何让一个模块同时支持 JDK 8 和 JDK 11+
不能双版本共存于同一个 module-info.java:JDK 8 会直接拒识该文件,导致编译失败。唯一可行路径是源码分离 + 构建时条件切换。
性能与兼容性影响:JDK 8 下走传统 classpath 加载,JDK 11+ 启用模块验证和强封装,两者启动速度、内存占用、反射行为都不同。
- 推荐结构:主模块代码放
src/main/java,module-info.java单独放在src/modular/java - Maven 中用
profile控制是否启用maven-compiler-plugin的<source>11</source>和<modulepath></modulepath> - 不要用
Automatic-Module-NameMANIFEST.MF 来“假装”模块化——它不提供访问控制,也触发不了opens行为 - 最易忽略的一点:JDK 9+ 的
java.base默认导出所有包,但 JDK 16 起对部分内部 API(如sun.misc.Unsafe)彻底封禁,连--add-opens都无效










