opens是JPMS中专为运行期反射访问设计的权限声明,仅允许指定模块通过反射访问本模块某包下所有成员(含private/package-private),不开放编译期访问或实例化权限。
在模块化 java(jpms)环境下,spring aop、hibernate 等框架依赖反射访问目标类的私有或包级成员来创建动态代理、注入字段、处理序列化等。但 exports 不管用——它只允许正常调用;真正卡住 aop 的,是 反射权限被模块系统默认封锁。而 opens 就是专为这类场景设计的“反射通行证”。
明确 opens 的作用边界
opens 不影响编译,也不让其他模块“直接 new 一个包私有类”,它唯一干的事是:在运行期,允许指定模块通过反射(setAccessible(true)、getDeclaredField 等)访问本模块中某包下的所有成员,包括 private 和 package-private 字段与方法。
- 如果你的业务模块里有
com.example.service.internal包,且 Spring AOP 需要代理其中的私有方法,就必须opens com.example.service.internal;或更精确地opens com.example.service.internal to spring.aop; - 若使用的是未命名模块(如传统 Spring Boot 打包的 fat jar),目标模块写
all-unnamed即可,但安全性较低 -
opens必须写在你自己的module-info.java中,不能靠第三方模块帮你开
匹配 AOP 典型场景配置 opens
Spring AOP 常见的反射瓶颈集中在三类位置,对应不同 opens 策略:
-
代理目标类含私有字段:比如实体类用
@Data生成私有id、createdAt,JPA/Hibernate 反射设值失败 → 在实体所在模块中opens com.example.model; -
切面需访问被代理类的包级方法:例如自定义注解切面想调用同包下工具方法 →
opens com.example.aspect.util to spring.aop; -
跨模块事务/安全代理失效:当
@Transactional方法在模块 A,而事务管理器在模块 B,B 需反射调用 A 的私有回调 → A 模块应opens com.example.service to spring.transaction;
避免滥用 all-unnamed,优先定向授权
很多项目为快速修复 InaccessibleObjectException,直接加 JVM 参数 --add-opens java.base/java.lang=all-unnamed。这虽能跑通,但等于把整个 JDK 内部反射大门敞开,违背最小权限原则。
- 正确做法是在自己的
module-info.java中,只对真正需要反射的包做opens ... to ... - 查清哪个框架模块触发了访问(看异常堆栈里的类名,如
org.springframework.aop.framework.CglibAopProxy),再填对应模块名 - 不确定目标模块名时,可用
jdeps --list-deps your-app.jar查依赖模块,或启动时加--show-module-resolution观察模块加载过程
与 Spring Boot 2.6+ 模块化兼容要点
Spring Boot 官方尚未完全模块化,其 starter 多为未命名模块。因此你的业务模块若声明了 module-info.java,需注意:
- 确保
requires spring.boot;等语句存在,否则编译不通过 - Spring Boot 自动配置类(如
DataSourceAutoConfiguration)可能反射访问你模块中的非导出类 → 你的模块需opens对应包给all-unnamed,或改用@SpringBootTest(classes = ...)显式指定测试类范围 - 若启用
spring.aot.enabled=true(AOT 编译),部分反射路径会被提前固化,此时opens仍需保留,否则原生镜像运行时报错










