使用LambdaMetafactory在从其他类加载器获取的类实例上调用one-arg方法 [英] Use LambdaMetafactory to invoke one-arg method on class instance obtained from other classloader

查看:236
本文介绍了使用LambdaMetafactory在从其他类加载器获取的类实例上调用one-arg方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于此stackoverflow答案 ,我试图使用反射实例化一个类,然后使用 LambdaMetafactory :: metafactory 调用一个参数方法(我尝试使用反射,但它很慢) 。

Based on this stackoverflow answer, I am attempting to instantiate a class using reflection and then invoke a one-argument method on it using LambdaMetafactory::metafactory (I tried using reflection, but it was rather slow).

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

More concretely, I want to create an instance of com.google.googlejavaformat.java.Formatter, and invoke its formatSource() method with the following signature: String formatSource(String input) throws FormatterException.

我定义了以下功能界面:

I have defined the following functional interface:

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

我正在尝试执行以下代码:

and am attempting to execute the following code:

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 失败,但出现以下异常:

When I run this code, the call to LambdaMetafactory::metafactory fails with the following exception:

    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 文档,但无法弄清楚我做错了什么。我希望其他人能够。

I've read through a number of stackoverflow answers about LambdaMetafactory and read the LambdaMetafactory documentation, but have not been able to figure out what I am doing wrong. I am hoping that somebody else will be able to.

提前感谢您的帮助。

推荐答案

MethodHandles.lookup()返回的 MethodHandles.Lookup 实例封装了调用方的context,即创建新类加载器的类的上下文。正如例外所述,从此上下文中看不到类型 Formatter 。您可以将此视为模仿操作的编译时语义的尝试;如果您在代码中放置了语句 Formatter.formatSource(sourceText),那么由于类型不在范围内,它也不会起作用。

The MethodHandles.Lookup instance returned by MethodHandles.lookup() encapsulates the caller’s context, that is, the context of your class which creates the new class loader. As the exception says, the type Formatter is not visible from this context. You can see this as an attempt to mimic the compile-time semantics of the operation; if you placed the statement Formatter.formatSource(sourceText) in your code, it wouldn’t work as well, due to the fact that the type is not in scope.

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

You can change the context class of the lookup object using in(Class), but when using MethodHandles.lookup().in(formatterClass), you’ll run into a different problem. Changing the context class of a lookup object will reduce the access level to align it with the Java access rules, i.e. you can only access public members of the class Formatter. But the LambdaMetafactory only accepts lookup objects having private access to their lookup class, i.e. lookup objects directly produced by the caller itself. The only exception would be changing between nested classes.

因此在(formatterClass)中使用 MethodHandles.lookup()。结果为无效的来电者:com.google.googlejavaformat.java.Formatter ,因为您(来电者)不是格式化程序上课。或者从技术上讲,查找对象没有私有访问模式。

Therefore using MethodHandles.lookup().in(formatterClass) results in Invalid caller: com.google.googlejavaformat.java.Formatter, as you (the caller) are not that Formatter class. Or technically, the lookup object has not the private access mode.

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

The Java API doesn’t offer any (simple) way to get a lookup object to be in a different class loading context and having the private access (prior to Java 9). All regular mechanisms would involve the cooperation of the code residing in that context. That’s the point where developers often go the route of doing Reflection with access override to manipulate the lookup object, to have the desired properties. Unfortunately, the new module system is expected to become more restrictive in the future, likely breaking these solutions.

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

Java 9 offers a way to get such a lookup object, privateLookupIn, which requires the target class to be in the same module or its module to be opened to the caller’s module to permit such an access.

由于您要创建一个新的 ClassLoader ,因此您可以使用类加载上下文。因此,解决问题的一种方法是向其添加另一个类,它创建查找对象并允许您的调用代码检索它:

Since you are creating a new ClassLoader, you have hands on the class loading context. So, one way to solve the problem, is to add another class to it, which creates the lookup object and allows your calling code to retrieve it:

    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 一次添加一个等价于

This subclasses URLClassLoader to call defineClass once in the constructor to add a class being equivalent to

public interface GimmeLookup {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
}

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

Then, the code reads the lookup field via Reflection. The lookup object encapsulates the context of GimmeLookup, which is defined within the new URLClassLoader, and is sufficient to access the public method formatSource of the public com.google.googlejavaformat.java.Formatter.

接口 FormatInvoker 将是可以访问该上下文,因为您的代码的类加载器将成为创建的 URLClassLoader 的父级。

The interface FormatInvoker will be accessible for that context, as your code’s class loader will become the parent of the created URLClassLoader.

一些额外的注意事项:


  • 当然,这只会比任何其他反光效率更高访问,如果您经常使用生成的 FormatInvoker 实例来补偿创建它的成本。

  • Of course, this can only become more efficient than any other reflective access, if you use the generated FormatInvoker instance sufficiently often to compensate for the costs of creating it.

我删除了 Thread.currentThread()。setContextClassLoader(cl); 语句,因为它在此操作中没有意义,但实际上是安静的,因为你没有把它放回去,所以线程后来保持对关闭的 URLClassLoader 的引用。

I removed the Thread.currentThread().setContextClassLoader(cl); statement, as it has no meaning in this operation, but is, in fact, quiet dangerous as you didn’t set it back, so the thread kept a reference to the closed URLClassLoader afterwards.

我简化了 toArray 调用 urls.toArray(新URL [0])本文提供了一个非常有趣的视图,指出了指定集合大小的用途到阵列。

I simplified the toArray call to urls.toArray(new URL[0]). This article provides a really interesting view on the usefulness of specifying the collection’s size to the array.

这篇关于使用LambdaMetafactory在从其他类加载器获取的类实例上调用one-arg方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆