从未命名模块创建 MethodHandle 时如何解决 IllegalAccessError

问题描述 投票:0回答:1

我正在尝试为 JDK 21 上

MethodHandle
记录的任何构造函数获取
com.sun.tools.javac.code.TypeMetadata.Annotations
。这是
TypeMetadata
的源代码(来自 OpenJDK):

https://github.com/openjdk/jdk/blob/acd93102348f592d6f2e77a4bff6037edf708d55/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeMetadata.java

这是我的代码:

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 java-module methodhandle
1个回答
0
投票

您遇到的错误是由于 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。

© www.soinside.com 2019 - 2024. All rights reserved.