在 Java 9+ 模块化中,反射读取私有字段注解必须使用 opens(而非 exports),并精准声明 opens com.example.model to ALL-UNNAMED 或 java.base;运行时需配合 --add-opens 参数,且须通过实际反射调用和模块依赖验证生效。
在 java 9+ 模块化(jpms)环境下,反射工具(如获取字段上的自定义注解)常因模块封装而失败——默认情况下,模块不会导出私有成员,也不会开放对反射的深度访问。module-info.java 是破局关键,它不是“加个 requires 就完事”,而是要精准声明 开放(opens) 或 导出(exports),并配合运行时参数或模块策略才能让反射正常工作。
明确 opens 和 exports 的区别:什么时候该用哪个?
exports 只允许其他模块访问 public 类型及其 public 成员,对反射无用;opens 才是反射所需的——它允许其他模块(包括未命名模块,如传统 classpath 应用)通过反射访问该包内的所有成员(含 private 字段、方法),包括读取其上的注解。
- 若你的类库中某个包(如
com.example.model)里有带注解的私有字段,且外部反射工具(如 JSON 序列化器、校验框架)需读取这些注解,就必须opens com.example.model; - 若只希望 public 字段被反射读取(极少见),可用
exports,但无法满足注解扫描需求 - 支持按模块名精细控制:
opens com.example.model to com.fasterxml.jackson.databind;表示仅向 Jackson 模块开放反射权限,更安全
处理运行时反射调用方不在模块系统中的情况
很多反射工具(如 Spring Boot 2.x 早期版本、JUnit 4、或直接从 classpath 启动的工具)运行在 未命名模块(unnamed module) 下。此时,module-info.java 中的 opens ... to ... 必须显式指向 java.base 或 ALL-UNNAMED,否则反射会抛 InaccessibleObjectException。
- 兼容传统环境:用
opens com.example.model to java.base;(因为反射 API 属于 java.base) - 更宽松但需谨慎:用
opens com.example.model to ALL-UNNAMED;,允许所有 classpath 类访问(开发/测试常用) - 注意:JDK 17+ 默认启用强封装,不加 opens 声明 + 未配 --add-opens 参数,反射必失败
与 JVM 启动参数协同:开发、测试、生产分场景处理
module-info.java 声明是编译期契约,但运行时仍可能受限。尤其在测试阶段或集成遗留代码时,需配合 JVM 参数:
- 单元测试(如 Maven Surefire):在
pom.xml中配置<argline>--add-opens com.example.app/com.example.model=ALL-UNNAMED</argline> - Spring Boot 应用:启动脚本中添加
--add-opens java.base/java.lang=ALL-UNNAMED(Spring 自身也依赖此) - 生产部署建议:优先靠 module-info.java 的
opens ... to ...精准授权,避免全局--add-opens,减少安全面和维护成本
实战验证:三步确认注解反射是否真正生效
光写 opens 不代表成功。需实际验证字段注解能否被读取:
立即学习“Java免费学习笔记(深入)”;
- 写一个最小测试:用
Class.getDeclaredField("fieldName").getAnnotation(MyAnn.class),确保不抛异常且返回非 null - 检查模块图:运行
java --list-modules | grep your-module,再用jdeps --list-deps your-module.jar确认依赖与 opens 范围匹配 - 开启调试:加 JVM 参数
-Djdk.debug=true -XX:+TraceClassLoading,观察是否出现 “Unable to make field accessible” 类提示



















