gpt4 book ai didi

java - 使用 LambdaMetafactory 在从其他类加载器获取的类实例上调用单参数方法

转载 作者:行者123 更新时间:2023-11-30 07:46:15 26 4
gpt4 key购买 nike

基于 this stackoverflow answer ,我正在尝试使用反射实例化一个类,然后使用 LambdaMetafactory::metafactory 在其上调用单参数方法(我尝试使用反射,但速度很慢)。

更具体地说,我想创建一个 com.google.googlejavaformat.java.Formatter 的实例。 ,并调用它的formatSource()具有以下签名的方法:String formatSource(String input) throws FormatterException .

我定义了以下功能接口(interface):

@FunctionalInterface
public interface FormatInvoker {
String invoke(String text) throws FormatterException;
}

并尝试执行以下代码:
try (URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[urls.size()]))) {
Thread.currentThread().setContextClassLoader(cl);

Class<?> formatterClass =
cl.loadClass("com.google.googlejavaformat.java.Formatter");
Object formatInstance = formatterClass.getConstructor().newInstance();

Method method = formatterClass.getMethod("formatSource", String.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = lookup.unreflect(method);
MethodType type = methodHandle.type();
MethodType factoryType =
MethodType.methodType(FormatInvoker.class, type.parameterType(0));
type = type.dropParameterTypes(0, 1);

FormatInvoker formatInvoker = (FormatInvoker)
LambdaMetafactory
.metafactory(
lookup,
"invoke",
factoryType,
type,
methodHandle,
type)
.getTarget()
.invoke(formatInstance);

String text = (String) formatInvoker.invoke(sourceText);
} finally {
Thread.currentThread().setContextClassLoader(originalClassloader);
}

当我运行这段代码时,调用 LambdaMetafactory::metafactory失败,但有以下异常:
    Caused by: java.lang.invoke.LambdaConversionException: Exception finding constructor
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:229)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:304)
at com.mycompany.gradle.javaformat.tasks.JavaFormatter.formatSource(JavaFormatter.java:153)
... 51 more
Caused by: java.lang.IllegalAccessException: no such method: com.delphix.gradle.javaformat.tasks.JavaFormatter$$Lambda$20/21898248.get$Lambda(Formatter)FormatInvoker/invokeStatic
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:867)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1003)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1386)
at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:780)
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:226)
... 53 more
Caused by: java.lang.LinkageError: bad method type alias: (Formatter)FormatInvoker not visible from class com.delphix.gradle.javaformat.tasks.JavaFormatter$$Lambda$20/21898248
at java.lang.invoke.MemberName.checkForTypeAlias(MemberName.java:793)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:976)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000)
... 56 more

我已经阅读了许多关于 LambdaMetafactory 的 stackoverflow 答案。并阅读 LambdaMetafactory文档,但无法弄清楚我做错了什么。我希望其他人能够做到。

预先感谢您的帮助。

最佳答案

MethodHandles.Lookup MethodHandles.lookup() 返回的实例封装调用者的上下文,即创建新类加载器的类的上下文。正如异常所说,类型 Formatter在此上下文中不可见。您可以将其视为模仿操作的编译时语义的尝试;如果您将声明 Formatter.formatSource(sourceText)在您的代码中,由于类型不在范围内,因此它也无法正常工作。

您可以使用 in(Class) 更改查找对象的上下文类。 , 但是当使用 MethodHandles.lookup().in(formatterClass) ,你会遇到不同的问题。更改查找对象的上下文类将降低访问级别以使其与 Java 访问规则保持一致,即您只能访问 public类(class)成员Formatter .但是 LambdaMetafactory只接受具有 private access 的查找对象到他们的查找类,即由调用者本身直接生成的查找对象。唯一的异常(exception)是在嵌套类之间进行更改。

因此使用 MethodHandles.lookup().in(formatterClass)结果 Invalid caller: com.google.googlejavaformat.java.Formatter ,因为你(来电者)不是 Formatter类(class)。或者从技术上讲,查找对象没有 private访问模式。

Java API 不提供任何(简单)方法来让查找对象处于不同的类加载上下文中并具有 private访问(在 Java 9 之前)。所有常规机制都将涉及驻留在该上下文中的代码的合作。这就是开发人员经常采用通过访问覆盖进行反射来操作查找对象的路线,以获得所需的属性。不幸的是,新的模块系统预计在 future 会变得更加严格,可能会破坏这些解决方案。

Java 9 提供了一种获取此类查找对象的方法,privateLookupIn ,这要求目标类在同一个模块中,或者它的模块要对调用者的模块开放,以允许这样的访问。

由于您正在创建一个新的 ClassLoader ,你掌握了类加载上下文。因此,解决问题的一种方法是向其中添加另一个类,该类创建查找对象并允许您的调用代码检索它:

    try (URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[0])) {
{ byte[] code = gimmeLookupClassDef();
defineClass("GimmeLookup", code, 0, code.length); } }) {

MethodHandles.Lookup lookup = (MethodHandles.Lookup)
cl.loadClass("GimmeLookup").getField("lookup").get(null);
Class<?> formatterClass =
cl.loadClass("com.google.googlejavaformat.java.Formatter");

Object formatInstance = formatterClass.getConstructor().newInstance();

Method method = formatterClass.getMethod("formatSource", String.class);
MethodHandle methodHandle = lookup.unreflect(method);
MethodType type = methodHandle.type();
MethodType factoryType =
MethodType.methodType(FormatInvoker.class, type.parameterType(0));
type = type.dropParameterTypes(0, 1);

FormatInvoker formatInvoker = (FormatInvoker)
LambdaMetafactory.metafactory(
lookup, "invoke", factoryType, type, methodHandle, type)
.getTarget().invoke(formatInstance);

String text = (String) formatInvoker.invoke(sourceText);
System.out.println(text);
}
static byte[] gimmeLookupClassDef() {
return ( "\u00CA\u00FE\u00BA\u00BE\0\0\0001\0\21\1\0\13GimmeLookup\7\0\1\1\0\20"
+"java/lang/Object\7\0\3\1\0\10<clinit>\1\0\3()V\1\0\4Code\1\0\6lookup\1\0'Ljav"
+"a/lang/invoke/MethodHandles$Lookup;\14\0\10\0\11\11\0\2\0\12\1\0)()Ljava/lang"
+"/invoke/MethodHandles$Lookup;\1\0\36java/lang/invoke/MethodHandles\7\0\15\14\0"
+"\10\0\14\12\0\16\0\17\26\1\0\2\0\4\0\0\0\1\20\31\0\10\0\11\0\0\0\1\20\11\0\5\0"
+"\6\0\1\0\7\0\0\0\23\0\3\0\3\0\0\0\7\u00B8\0\20\u00B3\0\13\u00B1\0\0\0\0\0\0" )
.getBytes(StandardCharsets.ISO_8859_1);
}

这个子类 URLClassLoader调用 defineClass一次在构造函数中添加一个等效于的类
public interface GimmeLookup {
MethodHandles.Lookup lookup = MethodHandles.lookup();
}

然后,代码读取 lookup通过反射场。查找对象封装了 GimmeLookup 的上下文,在新的 URLClassLoader 中定义,并且足以访问 public方法 formatSourcepublic com.google.googlejavaformat.java.Formatter .

接口(interface) FormatInvoker将可用于该上下文,因为您的代码的类加载器将成为创建的 URLClassLoader 的父级.

一些附加说明:
  • 当然,如果你使用生成的 FormatInvoker,这只会变得比任何其他反射访问更有效。实例足够频繁地补偿创建它的成本。
  • 我删除了 Thread.currentThread().setContextClassLoader(cl);声明,因为它在此操作中没有任何意义,但实际上由于您没有将其设置回去,所以很危险,因此线程保留了对已关闭 URLClassLoader 的引用然后。
  • 我简化了 toArray调用urls.toArray(new URL[0]) . This article提供了一个非常有趣的观点,说明了将集合的大小指定给数组的有用性。
  • 关于java - 使用 LambdaMetafactory 在从其他类加载器获取的类实例上调用单参数方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50787116/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com