opens用于运行时开放模块内指定包,允许其他模块通过反射访问其所有成员(含private),但不导出api;需在module-info.java中声明,或对jdk包用--add-opens启动参数。
java 模块系统(jpms)中,opens 关键字用于在运行时显式开放模块内的包,使反射、动态代理等机制能访问该包中的类、字段或方法。当使用 proxy.newproxyinstance 或类似框架(如 openfeign、spring aop)生成代理类,并需要通过反射读写目标类的私有字段、调用非 public 方法,或访问模块内部 api(如 java.base/sun.security.*)时,若目标类位于强封装模块中,jvm 会直接拒绝访问,并抛出类似:
java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String java.lang.System$LoggerProxy.name accessible
或更常见的模块错误:
Module java.base does not 'opens java.lang' to unnamed module @xxxx
这类问题本质是模块系统阻止了跨模块反射访问 —— 而 opens 就是解决它的核心手段之一。
如何在模块化项目中正确使用 opens
✅ 在 module-info.java 中声明 opens
适用于你自己控制的模块,且需被反射/代理访问的包明确属于该模块。
module com.example.myapp {
requires java.base;
requires java.logging;
// 允许其他模块(包括 unnamed module)在运行时反射访问该包
opens com.example.myapp.internal to java.base, java.logging;
// 若需被所有模块(含 unnamed)访问,可简写为:
opens com.example.myapp.config;
}
⚠️ 注意:
opens只对运行时反射生效,不导出(exports)包,也不影响编译期可见性;它仅放松 JVM 的封装检查。
✅ 针对 JDK 内部包(如 java.base)——无法改 module-info?用 JVM 启动参数
你无法修改 java.base 模块的 module-info.class,所以必须在启动时强制开放:
java \ --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.util=ALL-UNNAMED \ --add-opens java.base/sun.security.action=ALL-UNNAMED \ -jar myapp.jar
-
--add-opens <source-module>/<package>=<target-module></target-module></package></source-module> -
ALL-UNNAMED表示允许所有未命名模块(即传统 classpath 应用、动态代理生成的类)访问 - 若使用 Maven 打包,可通过
maven-surefire-plugin或exec-maven-plugin透传这些参数
✅ Maven 构建时注入到 MANIFEST.MF(配合 maven-jar-plugin)
某些容器或部署环境要求 Add-Opens 出现在 JAR 的 MANIFEST.MF 中(如部分 OSGi/JLink 场景):
<plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-jar-plugin</artifactid><version>3.3.0</version><configuration><archive><manifestentries><add-opens>java.base/java.lang=ALL-UNNAMED</add-opens><add-opens>java.base/java.util=ALL-UNNAMED</add-opens></manifestentries></archive></configuration></plugin>
✅ 这样生成的 JAR 在被
java -jar启动时,JVM 会自动识别并应用这些Add-Opens规则(等效于命令行参数),无需额外配置。
动态代理场景下的典型适配点
当你手写或调试基于 Proxy 的代理逻辑时,以下环节最易触发 opens 相关异常:
- 代理目标对象的类在
java.*或jdk.*模块中(如System.Logger、InetAddress) - 使用
Field.setAccessible(true)访问私有字段(尤其在序列化/反序列化工具中) - 框架(如 Spring、Hibernate)通过反射调用
private构造器或@Autowired字段 - OpenFeign 的
Contract解析接口时,反射读取@RequestLine注解所在类的包路径
此时,只需确认:
- 目标类所在模块是否已
opens对应包(你自己写的模块 → 改module-info.java) - 若目标类属 JDK → 加
--add-opens启动参数 - 若部署为独立 JAR → 用
maven-jar-plugin注入Add-Opens到 MANIFEST
补充:opens vs exports vs open module
| 声明 | 作用范围 | 是否允许反射 | 是否导出 API | 典型用途 |
|---|---|---|---|---|
exports pkg; |
编译 & 运行期 | ❌(仍受封装限制) | ✅(public 类可见) | 正常模块间调用 |
opens pkg; |
仅运行期 | ✅(反射可访问) | ❌(不导出) | 动态代理、序列化、测试 |
open module name { ... } |
整个模块 | ✅(所有包默认 opens) | ❌(仍需 exports 才能被引用) | 快速迁移旧代码,不推荐生产使用 |
? 实践建议:优先最小化
opens粒度(只开必需包),避免open module;对 JDK 包一律用--add-opens控制,不污染模块定义。
不复杂但容易忽略










