当前位置: 首页 > 面试题库 >

使用LambdaMetafactory对从其他类加载器获得的类实例调用one-arg方法

胡禄
2023-03-14
问题内容

我试图使用反射实例化一个类,然后使用对其调用一个单参数方法LambdaMetafactory::metafactory(我尝试使用反射,但是速度很慢)。

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

我定义了以下功能接口:

@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并阅读了LambdaMetafactory文档,但是还无法弄清楚我在做什么错。我希望其他人能够做到。

预先感谢您的帮助。


问题答案:

MethodHandles.Lookup返回的实例MethodHandles.lookup()封装了调用者的上下文,即创建新类加载器的类的上下文。如异常所示,该类型Formatter在此上下文中不可见。您可以将其看作是模仿操作的编译时语义的尝试。如果将语句放置Formatter.formatSource(sourceText)在代码中,由于该类型不在范围内,那么它将无法正常工作。

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

因此,在中使用MethodHandles.lookup().in(formatterClass)results Invalid caller: com.google.googlejavaformat.java.Formatter,因为您(调用方)不是Formatter该类。或者从技术上讲,查找对象没有private访问模式。

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

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

由于创建的是new
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

该接口FormatInvoker可用于该上下文,因为您代码的类加载器将成为created的父级URLClassLoader

一些附加说明:

  • 当然,如果您FormatInvoker足够频繁地使用生成的实例来补偿创建它的成本,那么它只能比任何其他反射式访问更有效。

  • 我删除了该Thread.currentThread().setContextClassLoader(cl);语句,因为它在此操作中没有任何意义,但是实际上,由于您没有将其重新设置,因此非常危险,因此该线程URLClassLoader此后一直引用关闭的内容。

  • 我简化了对的toArray呼叫urls.toArray(new URL[0])。本文提供了一个非常有趣的观点,说明了为数组指定集合大小的有用性。



 类似资料:
  • 问题内容: 我正在做作业,遇到了一些错误。在一个类中,我有此方法: 现在,我收到错误消息“无法从静态上下文中引用非静态方法…”,因此我将getPoints()设置为静态方法,并将变量也设置为静态,并且它可以正常工作。但是在另一种打印对象的方法中,它不起作用(我相信是由于static关键字)。 所以我的问题是,有没有一种方法可以在不创建第二个Class实例的情况下调用方法?这是我所拥有的一般构想代码

  • 我仍然是Java的新手,我正在努力使这个程序适合我的任务。 问题是: 杰夫在你家附近经营着一家当地的零售店。他已与您签订合同,让您创建一个交互式应用程序,以帮助他增加员工工资。创建一个名为 Details 的类,该类将包含员工 ID 号、名字、姓氏和薪水的获取和设置方法。包括一个名为getUpdateSalary()的方法,该方法将使员工的薪水增加10%。在您的主类中,包括一个名为 () 的静态方

  • 现在我得到错误“non-static method conly be referenced from a static context...”,所以我将getPoints()设置为静态方法,同时将变量设置为静态,这样就可以工作了。但是在另一个打印对象的方法中,它就不起作用了(我相信这是因为static关键字的缘故)。 那么我的问题是,在所有这些之后,有没有一种方法可以调用一个方法,而不创建第二个类

  • Firebase onMessageReceived方法提供remoteMessage实例以获取发送的消息或通知。如何将remoteMessage实例从扩展FirebaseMessagingService的类发送到RecolyerViewAdapter以更新RecolyerView中的项列表?

  • 实际上,它的测试如果eat方法对宠物有效,但我也需要检查feedPet方法对玩家也有效。 任何想法或建议都非常感谢。

  • 问题内容: 如果我有两个类,而其中一个有一个要在其他类中使用的函数,那么该使用什么,这样就不必重写我的函数了? 问题答案: 有两种选择: 在您的类中实例化一个对象,然后在其上调用所需的方法 使用@classmethod将函数转换为类方法 例: 或使用继承(如果适用):