module-info.java不参与动态代理类生成,因其作用于静态类型,而代理类是运行时生成的匿名类,默认归属未命名模块;但目标接口需被exports、反射访问需对应opens,且--illegal-access=deny下opens为必要解法。

module-info.java 本身不参与动态代理类的生成过程,也无法直接控制代理类的包路径或字段可见性。动态代理(如 JDK Proxy 或 CGLIB)在运行时由 JVM 或第三方库生成字节码,其类定义、包名、访问修饰符均由代理创建逻辑决定,而非模块声明文件。
为什么 module-info 对动态代理类无效
模块系统(JPMS)的封装规则作用于编译期和加载期的静态类型引用,而动态代理类是运行时生成的匿名或合成类,它们:
- 不包含 module-info.class,不属于任何命名模块
- 默认归属到未命名模块(unnamed module),享有对所有已导出包的反射访问权(前提是模块未禁用非法访问)
- 其包路径由代理工厂指定(如 Proxy.getProxyClass 默认放在 “$Proxy” 包下),与 provider 模块的 exports 无关
- 字段/方法的可见性取决于被代理目标类的修饰符及代理实现策略,不是由 module-info 的 opens 或 exports 控制
真正影响动态代理行为的关键点
虽然 module-info 不生成代理类,但它会间接限制代理能否成功创建和调用:
- 目标接口必须导出:若用 JDK Proxy 代理某个接口,该接口所在的包必须被其模块 exports,否则其他模块无法编译通过或加载该类型
- 需要反射访问时必须 opens:若代理需反射读写目标类的私有字段(如某些 ORM 或序列化框架),则目标模块必须对代理所在模块(或通用框架模块)opens 对应包
- 模块间 requires 必须完备:例如使用 java.lang.reflect.Proxy 的模块,需显式声明 requires java.base;(通常隐式满足),若依赖自定义注解处理器或 SPI,则对应模块也需正确 requires
控制代理类包路径的可行方式
包路径由代理创建 API 决定,可通过以下方式干预:
- JDK Proxy:无法指定包名,一律为 “$ProxyN” 形式,位于 unnamed module;可通过 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") 保存 class 文件用于调试
- CGLIB:可自定义 Enhancer.setClassLoader() 和 setNamingPolicy(),后者能重写类名生成逻辑,间接影响包路径(但通常仍为 default package)
- Byte Buddy / Javassist:提供完整类定义控制能力,可显式设置 packageName 和 name,生成到任意合法包下(只要该包在目标模块中可写且无冲突)
变量可见性问题的实际应对策略
代理对象访问字段失败,往往不是 module-info 配置错误,而是设计层面的可见性错配:
- 优先用 public getter/setter 替代直接字段访问,避免反射需求
- 若必须反射私有字段,确保 provider 模块声明:opens com.example.model to com.example.framework;
- 避免在非导出包内定义被广泛代理的接口或实体类——即使用了反射,IDE 和编译器也无法识别,易引发维护陷阱
- 检查是否启用了 --illegal-access=deny(Java 17+ 默认),该参数会彻底禁止未授权的反射,此时仅 opens 是合规解法











