我正在尝试为 JDK 21 上
MethodHandle
记录的任何构造函数获取 com.sun.tools.javac.code.TypeMetadata.Annotations
。这是 TypeMetadata
的源代码(来自 OpenJDK):
这是我的代码:
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
除了
IllegalAccessError
标志之外,还有什么办法可以绕过这个 --add-opens
吗?或者这是模块造成的基本限制?
您遇到的错误是由于 Java 9 中引入的 Java 平台模块系统 (JPMS) 造成的。使用 JPMS,即使您可以通过反射看到类或成员,但这并不意味着您可以访问它。 --add-opens 标志允许您在运行时打破封装,但它不会授予对类的私有成员或私有构造函数的访问权限。
TypeMetadata.Annotations 类是一个记录,记录生成一个私有构造函数(除了记录可能声明的任何公共构造函数之外)。当您将 MethodHandles.lookup().findConstructor() 方法与 void.class MethodType 一起使用时,将访问此私有构造函数。
要解决这个问题:
使用反射而不是 MethodHandles:这不如 MethodHandles 高效,但对于某些用例来说,它可能是一个可行的解决方法。
Constructor<?> constructor = TypeMetadata.Annotations.class.getDeclaredConstructor();
constructor.setAccessible(true);
TypeMetadata.Annotations instance = (TypeMetadata.Annotations) constructor.newInstance();
自定义查找:如果您有权访问 TypeMetadata 类的内部,则可以提供具有私有访问权限的自定义 MethodHandles.Lookup 对象。这通常是通过在目标类中提供静态工厂方法来完成的。
// Inside TypeMetadata class
public static MethodHandles.Lookup privateLookup() {
return MethodHandles.lookup();
}
然后在你的测试代码中:
MethodHandles.Lookup lookup = TypeMetadata.privateLookup();
但是,这种方法需要修改 TypeMetadata 类,这对于您的用例来说可能不可行。
使用不安全:不建议在生产代码中这样做,因为它可能导致 JVM 崩溃、不可预测的行为,并且可能会在未来的 Java 版本中删除。但对于某些用例,尤其是测试或原型设计,它可能是绕过这些限制的一种方法。
要使用 Unsafe,您通常会使用反射来获取 Unsafe 实例,然后使用其方法来实例化对象或访问字段/方法。再次强调,这是最后的手段,应谨慎使用。
重新考虑需求:如果您尝试访问 JDK 的内部部分以实现合法用例,则可能值得考虑是否有更“公开”或“官方”的方法来实现您想要做的事情。访问内部 API 可能会导致代码变得脆弱,并且可能会随着每次 JDK 更新而崩溃。
一般来说,尽可能避免依赖内部 JDK API 是个好主意,因为它们可能会在没有警告的情况下发生更改,并且不适合公共使用。如果您使用它们进行测试或调试,这是一回事,但对于生产代码,最好尽可能坚持使用公共 API。