如何解决从无名模块创建MethodHandle时的IllegalAccessError
我在JDK 21中尝试获取com.sun.tools.javac.code.TypeMetadata.Annotations记录的无参构造方法的MethodHandle,代码如下:
import com.sun.tools.javac.code.TypeMetadata; import java.lang.invoke.*; import java.util.*; class TestMH { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType mt = MethodType.methodType(void.class); MethodHandle mh = lookup.findConstructor(TypeMetadata.Annotations.class, mt); System.out.println(mh); } }
使用以下命令编译:
javac --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED TestMH.java
运行命令:
java --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED TestMH
执行后出现报错:
Exception in thread "main" java.lang.IllegalAccessException: no such constructor: com.sun.tools.javac.code.TypeMetadata$Annotations.<init>()void/newInvokeSpecial at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:911) at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:994) at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3750) at java.base/java.lang.invoke.MethodHandles$Lookup.findConstructor(MethodHandles.java:2837) at TestMH.main(TestMH.java:9) Caused by: java.lang.IllegalAccessError: class TestMH tried to access method 'void com.sun.tools.javac.code.TypeMetadata$Annotations.<init>()' (TestMH is in unnamed module of loader 'app'; com.sun.tools.javac.code.TypeMetadata$Annotations is in module jdk.compiler of loader 'app') at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method) at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:962) at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:991) ... 3 more
请问除了--add-opens参数外,是否有其他方法绕过该IllegalAccessError?还是这是模块机制的根本限制?
核心问题与解决方案
首先纠正一个基础错误:TypeMetadata.Annotations是Record类,它默认不会生成无参构造方法——Record类的规范构造器(canonical constructor)参数列表与它的组件完全匹配(这里组件是List<? extends Annotation>),你代码中指定的void.class对应的无参构造根本不存在,这是报错的直接原因之一。
即便你使用正确的构造方法签名,模块权限限制仍然存在。以下是无需额外--add-opens参数的合法解决方案:
方案1:通过MethodHandles.privateLookupIn合法获取构造方法的MethodHandle
JDK 9+提供的privateLookupIn API允许跨模块获取私有成员的访问权限,只要你能拿到目标类的引用(这一步通过--add-exports已经满足)。修改代码如下:
import com.sun.tools.javac.code.TypeMetadata; import java.lang.invoke.*; import java.lang.reflect.Constructor; import java.util.List; class TestMH { public static void main(String[] args) throws Throwable { // 获取Annotations的规范构造方法(参数为List<? extends Annotation>) Constructor<TypeMetadata.Annotations> ctor = TypeMetadata.Annotations.class.getDeclaredConstructor(List.class); // 使用privateLookupIn获取跨模块的Lookup实例 MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(TypeMetadata.Annotations.class, MethodHandles.lookup()); // 将反射构造器转换为MethodHandle MethodHandle mh = lookup.unreflectConstructor(ctor); // 调用构造方法生成实例(传入空列表测试) TypeMetadata.Annotations instance = (TypeMetadata.Annotations) mh.invoke(List.of()); System.out.println(instance); } }
编译命令保持不变:
javac --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED TestMH.java
运行时无需添加--add-opens,因为privateLookupIn是模块系统允许的合法跨模块访问方式。
方案2:避免直接依赖内部API
com.sun.tools.javac下的类属于JDK内部API,没有向后兼容性保证。如果你的需求是处理编译元数据,建议使用官方标准的javax.tools API(比如JavaCompiler、JavaFileObject等),通过合法入口获取元数据,而非直接调用内部类的构造方法。
关于模块机制的限制
模块机制的核心设计就是封装内部API,禁止外部代码随意访问非导出的私有/包私有成员。如果不使用--add-opens(强制开放包)或privateLookupIn(合法授权访问),无法绕过这个限制——这是模块系统的根本约束,目的是提高JDK的稳定性和安全性。
内容的提问来源于stack exchange,提问作者msridhar

